内存管理
Java的内存管理采用“自动内存管理”机制。
JDK1.7内存模型
JVM 运行时有5个区域:程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。
JDK1.8内存模型
JVM运行时有4个区域:程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、堆(Heap)。
1.8同1.7在内存模型方面的变化有:
1)方法区变化【方法区仍然是堆的一个逻辑部分】
1.8同1.7比,最大的差别就是:元数据区取代了永久代,就是JDK8没有了PermSize相关的参数配置了。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
方法区与永久代的区别?
方法区只是JVM规范定义,而永久代为具体的实现,元空间也是方法区在jdk1.8中的一种实现。
为什么废除永久代?
官方文档:移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
解释:PermGen很难调整,PermGen中类的元数据信息在每次FullGC的时候可能被收集,但成绩很难令人满意。而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class总数,常量池的大小,方法的大小等。并且永久代内存经常不够用发生内存泄露。
总结:永久代分配空间难确定,且容易引发内存泄漏,为了融合 JRockit VM 的优点将永久代废除。
2)运行时常量池变化【运行时常量池仍然是方法区的一部分】
在近三个JDK版本(1.6、1.7、1.8)中, 运行时常量池(Runtime Constant Pool)的所处区域一直在不断的变化,在JDK1.6时它是方法区的一部分;1.7又把他放到了堆内存中;1.8之后出现了元空间,它又回到了方法区。
内存区域以及介绍
内存空间 | 线程共享 | 存储的数据类型 | 说明 | 异常 | 配置 |
---|---|---|---|---|---|
程序计数器 | 线程私有 | 1字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 2在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 |
1是一块较小的存储空间 2线程私有。每条线程都有一个程序计数器。 3是唯一一个不会出现OutOfMemoryError的内存区域。 4生命周期随着线程的创建而创建,随着线程的结束而死亡 |
不会抛出OutOfMemoryError异常。 | 无 |
Java栈 | 线程私有 | 局部变量表(存放基本数据类型变量、引用类型的变量、returnAddress类型的变量。) 操作数栈 动态链接 方法出口信息 |
虚拟机栈描述的是Java方法执行的内存模型: 每个方法被执行时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息. 每个方法被调用至返回的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程(VM提供了-Xss来指定线程的最大栈空间, 该参数也直接决定了函数调用的最大深度). |
会抛出StackOverFlowError和OutOfMemoryError异常。 StackOverflowError:线程请求的栈深度大于虚拟机所允许的最大深度;OutOfMemoryError:虚拟机在扩展栈时无法申请足够的内存空间 |
-Xss |
本地方法栈 | 线程私有 | 本地方法的: 局部变量表(存放基本数据类型变量、引用类型的变量、returnAddress类型的变量。) 操作数栈 动态链接 方法出口信息 | 与Java Stack作用类似, 区别是Java Stack为执行Java方法服务, 而本地方法栈则为Native方法服务, 如果一个VM实现使用C-linkage模型来支持Native调用, 那么该栈将会是一个C栈, 但HotSpot VM直接就把本地方法栈和虚拟机栈合二为一. | 会抛出StackOverFlowError和OutOfMemoryError异常。 StackOverflowError:线程请求的栈深度大于虚拟机所允许的最大深度;OutOfMemoryError:虚拟机在扩展栈时无法申请足够的内存空间 |
-Xss |
Java堆 | 线程共享 | 所有new出的对象,对象的引用放在栈中 | 堆是用来存放对象的内存空间。 几乎所有的对象都存储在堆中。 |
OutOfMemoryError 堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的(通过-Xmx和-Xms 控制),因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError。 |
-Xms -Xmx -Xmn |
方法区 | 线程共享 | 已被虚拟机加载的类信息 常量 静态变量 即时编译器编译后的代码等数据 | 1线程共享 方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的。整个虚拟机中只有一个方法区。 2永久代 方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区称为老年代。 3内存回收效率低 方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效。对方法区的内存回收的主要目标是:对常量池的回收 和 对类型的卸载。 4Java虚拟机规范对方法区的要求比较宽松。 和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。 |
JDK7及其之前版本 OutOfMemoryError 根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。 |
-XX:PermSize -XX:MaxPermSize |
JVM相关异常
JVM内存的异常有两种,分别是栈溢出和内存溢出。
-
栈溢出是
StackOverflowError
,对应虚拟机栈和本地方法栈,当线程请求的栈深度大于虚拟机所允许的深度时就会抛出该异常。 -
内存溢出是
OutOfMemoryError
,一般对应线程共享区域,如堆和元数据区。当内存不足以分配对象空间,而堆或方法区又无法扩展时,就会抛出该异常。
比如对应堆区的OutOfMemoryError: Java heap space
,对应元数据区的OutOfMemoryError: Metaspace
。如果Java虚拟机栈容量可以动态扩展 ,当栈扩展时无法申请到足够的内存也会抛出OutOfMemoryError
。