JVM垃圾回收机制总结
对于垃圾回收机制我先抛出三个问题:
①哪些内存需要回收?
②什么时候回收?
③如何回收?
下面我们主要针对这三个问题来研究JVM GC
一、哪些内存需要回收?
1.JAVA使用可达性分析法来判断对象是否需要回收。
这个算法的基本思路是通过一系列称为“GC ROOTS”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOTS没有任何引用链的话,则此对象不可用。可回收。
GC ROOTS对象包括以下几种:
①虚拟机栈(栈帧中的本地变量表)中引用的对象
②方法区中类静态属性引用的对象
③方法区中常量引用的对象
④本地方法栈JNI引用的对象
2.对象在作可达性分析后如果没有与任何GC ROOTS关联那么将会被标记筛选,如果它覆盖了finalize()方法则它将会被放置在一个F-Queue队列中,并由一个Finalizer线程去执行,如果在finalize方法中成功拯救了自己(将this引用赋值给每个类变量或者成员变量),则可避免被回收。但是强烈不建议使用对象的finalize()方法。在这里我只是把我知道的记录一下。
二、什么时候回收?
1.安全点
程序在执行时并不是在所有地方都能停下来进行GC,只有到达安全点才能暂停。对于Safepoint,需要考虑的问题是如何在GC发生时让所有线程都跑到最近的安全点再停顿下来。这里有两种方案可选:
①抢占式中断
抢占式中断不需要线程的执行代码主动去配合,在GC发生时,首先把所有线程全部中断,如果发现线程中断的地方不在安全点上,就恢复线程,让它跑到安全点上。
②主动式中断
主动式中断不直接对线程操作,仅仅设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起,轮询标志和安全点是重合的,另外加上创建对象需要分配内存的地方。
2.安全区域
安全区域是指在一段代码中,引用关系不会发生变化,在这个区域的任何地方开始GC都是安全的。
线程执行到safe region中的代码时,首先标识自己进入safe region,那在这段时间里JVM要发起GC时就不用管那些已经标识自己为safe region状态的线程了。当线程要离开safe region时,检查系统是否已经完成了根节点枚举,如果完成了,那线程就继续执行否则就等待直到可以离开safe region的信号为止。
三、如何回收?
1.回收算法:
1)标记-清除算法
缺点:①效率问题,标记和清除两个过程的效率都不高;②空间问题,清除后会产生大量不连续的内存碎片,导致大对象无法分配而提前出发GC
2)复制算法
复制算法是将内存分为大小相等的两块,每次只是使用其中的一块。当一块内存使用完之后,将还存活的对象复制到另一块内存中,然后清空内存。
缺点:内存容量只使用了一半。
现在的商业虚拟机都采用这种方式来回收新生代。使用一块eden和两块survivor区,eden:survivor=8:1,每次只使用eden和一块survivor,然后将存活对象复制到另一块survivor上。
注意,这里我们不能保证每次回收都只有不多于10%的对象存活,当survivor空间不够时需要依赖其他内存(老年代)进行分配担保。
3)标记-整理算法
标记过程与标记-清除算法相同,后续不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存
4)分代收集算法
针对不同的内存区域采用不同的回收算法,比如年轻代采用复制算法,老年代采用标记-清除或者标记-整理算法。
四、垃圾回收器
1.serial收集器
单线程收集器,年轻代采用复制收集算法,老年代采用标记-整理算法
2.ParNew收集器
是Serial收集器的多线程版本
3.Parallel Scavenge收集器
采用复制算法的多线程新生代收集器,与ParNew不同的是,其目的是达到一个可控制的吞吐量。(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))
4.Parallel Old收集器
Parallel Scavenge的老年代版本。
5.CMS收集器
以获取最短回收时间为目标的收集器,基于标记-清除算法
6.G1收集器
特点:
①并行与并发
②分代收集
G1不需要与其他收集器配合,管理整个GC堆
③空间整合
从整体上来看属于标记-整理算法,从局部看属于复制算法,不会产生内存碎片
④可预测停顿