引言:上一遍博客总结了各种垃圾收集器,对于非并发垃圾回收器还是比较好理解的。在此记录一下关于CMS、G1并发垃圾收集器的一些疑问。分为初始标记(STW),并发标记、重新标记(STW)、并发清除(筛选回收).
1 初始标记找出Gc Roots为什么那么快?OopMap是什么?
根据可达性分析算法首先要找到根节点,但是作为Gc Roots的对象主要在全局性和执行上下文中(两栈两方法),现在的java应用仅仅一个方法区就上百兆再加上线程栈就更大了,如果是逐个检查肯定会花费大量的时间。但是初始标志阶段又号称速度快。具体是怎么实现的呢? Java虚拟机采用的是准确式gc,所以当系统停顿下来的时候,并不需要不漏的检查所有区域的gc roots。虚拟机有办法直接哪些地方存在这对象引用。在HotSpot的实现中,是使用一组OopMap的数据结构达到这个目的。在类加载完成的时候,HotSpot就把对象内什么偏移量上有什么类型的数据计算出来了,在JIT编译的过程中也会在特定位置记录下栈和寄存器中哪些位置存放的是引用。OopMap记录了该引用对象的有效范围。在gc扫描时候就可以直接得到Gc Roots信息了。
OopMap会在JIT编译过程中执行到指定位置记录引用。涉及到另一个名词(安全点),在oopmap的协助下可以快速的完成gc roots的查找。但是不可能为栈中的每一条指令都生成oopMap,要不然这样数据量就太大了。为了减少空间,实际上JVM只会在安全点记录信息。即程序执行时并非所有地方都可以停顿下来进行gc。只能到达安全点才能暂停。 这些特定的位置主要在:循环的末尾、方法临返回前 / 调用方法的call指令后、可能抛异常的位置。只有在所有线程都跑到安全点才能停顿进行gc,jvm不可能让一个线程等另一个线程延长 开始gc时间,jvm采用的主动式中断线程的,在要开始gc时,会设置一个标识位,各个线程执行到安全点的时主动轮询这个标识,如果为真就挂起线程等待垃圾回收。
安全点解决了如何进入gc的问题,但是安全点只保证了运行时的线程在短时间内到达安全点。但是对于不执行处于睡眠或者堵塞的线程没处理,显然JVM不可能直接给这些cpu分配时间跑到安全点。对于这种情况JVM使用安全区的来解决。安全区域是只在一段代码之中,引用关系不会改变,在这个区域任何地方执行gc都是安全的。当线程执行到安全区域的代码时,首先会标记进入安全区,这段时间进行gc时就不会管着这个线程。线程在要离开安全区域的时候,必须等到gc结束才离开。
2 并发标记gc线程和用户线程同时进行,是怎么保证引用关系安全的呢?
使用的是三色标记法。这个算法将gc中的对象分为三种:黑、灰、白:
1. 黑色:搜索树完成的对象,包括其本身和该链下的子对象
2. 灰色:搜索中的对象,自身已经搜索完成但是其子对象未标记
3. 白色:还未开始标记的对象(标记完之后还是白色就是垃圾对象)
图片说明:A->B->C,首先Gc Roots标志A变为灰色,接着就是搜索A的子节点,找到B并标志为灰色,待A对象所有直接引用 的对象标记完,A标志为黑色。接着就是对B的子对象进行标记。等B所有的子对象标为灰色后B标为黑色,一层层搜索标志知道底层。多标-浮动垃圾(无影响) 假设B已经标志为灰色或者黑色了。这是用户线程把A->B的引用改成了A->C,但是这时B已经不是白色了。这次垃圾回收不会处理。对于垃圾回收来说B就是多标的浮动垃圾要等下一次垃圾回收处理。对程序并没影响,可能等下一次垃圾回收处理。漏标—影响程序运行 上述情况对B来说是多标的对象,但是C就是漏标的。根据三色标记算法,A已经标记为黑色了,C就永远不会标记到,最后C对象会当做垃圾回收掉直接影响程序。
漏标的两个条件
* 有至少一个黑色对象在自己被标记之后指向了这个白色对象
* 所有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用
CMS的解决方案:写屏障+增量更新( Incremental Update)
Incremental Update关注的是引用关系的增加,当发现有可达的引用增加,便开始重新标记。 写屏障在A->C时,写屏障相当于AOP会把黑节点标志为灰色,在后续的重新标志阶段搜索标记。这样的效率慢一点
G1的解决方案: 原始快照(SATB)
在B对C进行删除的时候会把要消失的C应用推到GC的堆栈,保证C可以被扫描到,在重新标记阶段就可以扫描标记到。 SATB通过快照记录引用关系,一旦发现有引用删除,通过查看快照记录的引用关系,重新标记。也是会出现浮动垃圾的
Incremental Update算法和SATB算法对比
SATB 算法是关注引用的删除。(B->C 的引用),而Incremental Update 算法关注引用的增加。(A->C 的引用)。
CMS使用 Incremental Update 算法,变成灰色的成员还要重新扫,重新再来一遍,效率太低了而G1是直接把删掉的对象统一重新标记。 所以 G1 在处理并发标记的过程比 CMS 效率要高,这个主要是解决漏标的算法决定的。
总结:并发标记是可能出现漏标的现象,但是后续的重新标记将重新对这些修改过的对象进行重新标记。会存在浮动垃圾但是不会出现错标导致程序异常。重新标志阶段结束之后肯定是只有黑色和白色两种对象。
3 并发处理是怎么保证安全的?并发期间产生的新垃圾怎么处理?
经过重新标记之后,gc对象只有黑色和白色了,肯定不存在黑色会重新指向白色而出现漏标现象(参考漏标的两个必要条件)因此并发清除是安全的
并发清除过程中会有新的新生代升级为老生代,这部分对象肯定不能直接回收,查询了网上资料有两种观点1 并发期间产生的垃圾或升级对象直接标志为黑色不进行垃圾回收, 2 并发阶段生成的对象和垃圾分配到一块与三色标记无关的内存区域,清理的时候不处理那块逻辑。个人倾向于第二种,深入JVM虚拟机在介绍CMS垃圾器特点是强调CMS不能在全部用完了才触发gc,会设置一个比例当老年代内存达到这个阈值会触发因为要留点内存看见给并发清理时用户线程使用。(这个问题不确定,希望大佬解惑)
以上是我关于并发垃圾收集器的一些疑问和见解,期待各位大佬的指正。