Gc的三色标记算法,G1使用的是Rset记录老年代被---引用的指针。同时,CMS使用的是对在gc遍历过程中修改过指针指向的对象再进行扫描一遍。G1使用了STAB算法,因此在标记开始时候就是一个快照。当在遍历过程中,指针的修改也还是以开始的快照原始引用为准,因此,当遍历过程中修改的指针指向可能导致浮动垃圾,这些垃圾要到下一次的gc中消除。
Rset的作用其他文章讲的都模凌两可。这里的这篇文章讲的很清楚:
post-write barrier记录了跨Region的引用更新,更新日志缓冲区则记录了那些包含更新引用的Cards。一旦缓冲区满了,Post-write barrier就停止服务了,会由Concurrent refinement threads处理这些缓冲区日志。 RSet究竟是怎么辅助GC的呢?在做YGC的时候,只需要选定young generation region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个old generation。 而mixed gc的时候,old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region。所以RSet的引入大大减少了GC的工作量。
初始标记: 需要停顿,但是耗时很短. 通常初始标记阶段会跟一次新生代收集一起进行
根分区扫描: 不需要停顿,这个阶段G1开始扫描survivor分区,所有被survivor分区所引用的对象都会被扫描到并将被标记. 该阶段不能发生新生代收集
并发标记: 不需要停顿,并发标记利用trace算法找出存活对象,并记录在一个bitmap中. 使用了SATB算法解决该过程中引用发生变化产生对象漏标问题
再次标记: 需要停顿,可并发执行. G1垃圾收集器会处理掉剩下的SATB日志缓冲区和所有更新的引用. 空的区域会直接被移除并回收。
清除回收: 需要停顿,可并发执行。 对各个Region的回收价值和成本记性排序,根据用户的期望进行回收。转移或拷贝存活的对象到新的未使用的heap区
————————————————
版权声明:本文为CSDN博主「阿早」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33460562/article/details/103202935
黑白灰的标记:https://www.jianshu.com/p/8d37a07277e0
三色标记
按照R大的说法:CMS的incremental update设计使得它在remark阶段必须重新扫描所有线程栈和整个young gen作为root;G1的SATB设计在remark阶段则只需要扫描剩下的satb_mark_queue。)
\Java虚拟机用了一个叫做CardTable(卡表)的数据结构来标记老年代的某一块内存区域中的对象是否持有新生代对象的引用,卡表的数量取决于老年代的大小和每张卡对应的内存大小,每张卡在卡表中对应一个比特位,当老年代中的某个对象持有了新生代对象的引用时,JVM就把这个对象对应的Card所在的位置标记为dirty(bit位设置为1),这样在Minor GC时就不用扫描整个老年代,而是扫描Card为Dirty对应的那些内存区域。
流程:
ZGC的染色指针因为“自愈”(Self-Healing)能力,所以只有第一次访问旧对象会变慢,而Shenandoah的Brooks转发指针是每次都会变慢。 一旦重分配集中某个Region的存活对象都复制完毕后,这个Region就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉,因为可能还有访问在使用这个转发表。
ZGC最大的问题是浮动垃圾。
Brooks pointer分散在每个对象头上
,比较不容易引起false sharing:
每个阶段要做的事情如下:
Init Mark 并发标记的初始化阶段,它为并发标记准备堆和应用线程,然后扫描root集合。这是整个GC生命周期第一次停顿,这个阶段主要工作是root集合扫描,所以停顿时间主要取决于root集合大小。
Concurrent Marking 贯穿整个堆,以root集合为起点,跟踪可达的所有对象。 这个阶段和应用程序一起运行,即并发(concurrent)。这个阶段的持续时间主要取决于存活对象的数量,以及堆中对象图的结构。由于这个阶段,应用依然可以分配新的数据,所以在并发标记阶段,堆占用率会上升。
Final Mark 清空所有待处理的标记/更新队列,重新扫描root集合,结束并发标记。. 这个阶段还会搞明白需要被清理(evacuated)的region(即垃圾收集集合),并且通常为下一阶段做准备。最终标记是整个GC周期的第二个停顿阶段,这个阶段的部分工作能在并发预清理阶段完成,这个阶段最耗时的还是清空队列和扫描root集合。
Concurrent Cleanup 回收即时垃圾区域 -- 这些区域是指并发标记后,探测不到任何存活的对象。
Concurrent Evacuation 从垃圾收集集合中拷贝存活的对到其他的region中,这是有别于OpenJDK其他GC主要的不同点。这个阶段能再次和应用一起运行,所以应用依然可以继续分配内存,这个阶段持续时间主要取决于选中的垃圾收集集合大小(比如整个堆划分128个region,如果有16个region被选中,其耗时肯定超过8个region被选中)。
Init Update Refs 初始化更新引用阶段,它除了确保所有GC线程和应用线程已经完成并发Evacuation阶段,以及为下一阶段GC做准备以外,其他什么都没有做。这是整个GC周期中,第三次停顿,也是时间最短的一次。
Concurrent Update References 再次遍历整个堆,更新那些在并发evacuation阶段被移动的对象的引用。这也是有别于OpenJDK其他GC主要的不同,这个阶段持续时间主要取决于堆中对象的数量,和对象图结构无关,因为这个过程是线性扫描堆。这个阶段是和应用一起并发运行的。
Final Update Refs 通过再次更新现有的root集合完成更新引用阶段,它也会回收收集集合中的region,因为现在的堆已经没有对这些region中的对象的引用。
这是整个GC周期最后一个阶段,它的持续时间主要取决于root集合的大小。
Concurrent Cleanup 回收那些现在没有任何引用的Region集合。
Shenandoah 与 ZGC
Brooks 在原有对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己(类似句柄,一个是放在句柄池中,一个是放在对象头前面),如图:
在对象移动的时候我们只需要将Brooks Pointer 指向新对象,在对象访问过程中,只通一条mov
指令就可以完成对新对象的访问了,如图:
当写操作发生时,Shenandoah收集器是通过CAS(Compare And Swap)操作,来保证收集器线程或者用户线程只有其中之一可以进行修改操作,以此来保证并发时对象访问的正确性。