您的位置:首页 > 编程语言 > Java开发

java虚拟机----自动内存管理机制

2018-02-22 19:55 295 查看

java内存区域与内存异常之深入理解Java虚拟机JVM高级特性与最佳实践(周志明)心得

运行时数据区域

java虚拟机在执行java程序的时候,会把它所管理的内存划分为若干个不同的数据区域。

根据java虚拟机规范,java所管理的内存将会包括以下运行时数据区域

方法区

和堆区一样,是各个线程共享的内存区域,存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。

当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

虚拟机栈

线程私有的,生命周期和线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法执行的同时会创建一个栈帧(是方法运行期的基础数据结构)。用来存储局部变量表、操作栈、动态链表、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈道出栈的过程。

java虚拟机对这个区域规定了两个异常状况:

①如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常

②如果虚拟机栈可动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常

本地方法栈

和虚拟机栈所发挥的作用非常相似,区别是虚拟机栈为虚拟机执行java方法(也就是字节码服务),而本地方法栈则是为虚拟机使用到的Native方法服务。

本地方法区域也会抛出StackOverflowError异常和OutOfMemoryError异常



java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,此内存区域唯一的目的是存放对象实例。

java堆是垃圾收集器管理的主要区域,很多时候被称为GC堆。由于现在收集器都基本采用分代收集算法,所以java堆还可以细分为:新生代和老年代,再细致点可分为Eden空间,From Survivor空间,To Survivor空间。如果从内存分配的角度看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区。

java堆可以处在物理上不连续的内存空间中,只要逻辑上连续就行。如果堆中没有内存完成的实例分配,并且堆无法扩展时,将会抛出OutOfMemoryError异常。

程序计数器

是一块较小的内存空间,由于java虚拟机的多线程是通过线程轮流切换分配处理器执行时间的方式实现的,所以在任意的一个时刻,一个处理器只能执行一条线程的指令。为了线程切换后能恢复到正确位置,一个线程会有一个独立的程序计数器,用来记录程序执行到哪个命令(指令)。如果执行Native方法则计数器值为空,此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

垃圾收集器与内存分配策略

GC需要完成的三件事

那些内存需要回收

①引用计数器法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1,当引用失效时,计数器就减1,任何时刻计数器都为0的对象是不可能再被使用的。但是java语言没有选用程序计数器来管理内存其中主要原因是它很难解决对象之间的相互循环引用问题。

②根搜索算法

基本思路是通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的,这个对象就是要回收的对象。

GC Roots的对象包括下面几种

虚拟机栈(栈帧中的本地变量表)中引用的对象

方法区中的类静态属性引用的对象

方法区中的常量引用对象

本地方法栈中JNI(即一般说的Native方法)的引用对象

什么时候回收

当一个对象到GC Roots不可达时,在下一个垃圾回收周期中尝试回收该对象,如果该对象重写了finalize()方法,并在这个方法中成功自救(将自身赋予某个引用),那么这个对象不会被回收。但如果这个对象没有重写finalize()方法或者已经执行过这个方法,也自救失败,该对象将会被回收。

如何回收

回收算法

①标清-除法 首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。缺点:一个是效率问题,标记和清除过程的效率都不高;另一个是空间问题,标记清楚之后会产生大量不连续的内存碎片,碎片过多可能会导致程序在以后的运行中,因为需要较大的对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

②复制算法 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上,最后将已经使用过的内存空间一次清理掉。缺点:内存缩小为原来的一半,代价高。

但是这种收集算法适合回收新生代,新生代的对象98%是朝生夕死,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor,当回收时将

Eden和Survivor中还存活的对象一次性的拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor。两者默认大小比为8:1。当Survivor空间不够用的时候需要依赖其他内存(这里指老年代)进行分配担保。

③标记–整理法 根据老年代的特点,有人提出了另外一种标记–整理算法,标记过程和标记–清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向另外一端移动,然后直接清理掉端边界以外的内存。

④分代收集算法 当前商业虚拟机的垃圾收集都采用“分代收集”算法,这种算法并没有什么新的思想,只是根据对象的存货周期将不同的内存划分为几块,一般是把java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。新生代采用复制算法,老年代因为对象存活率高采用标记-清理算法或标记-整理算法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: