再谈java的gc算法

聊聊Garbage Collector的SATB其中:Shenandoah在进行concurrent marking的时候采用了SATB的技术,文章中的这句话应该是错误的

G1垃圾回收器详解 SATB算法 大对象的申请是将多个连续的regin结合起来分配给大对象,对应的regin是不会分配给小对象使用的

Gc的三色标记算法,G1使用的是Rset记录老年代被---引用的指针。同时,CMS使用的是对在gc遍历过程中修改过指针指向的对象再进行扫描一遍。G1使用了STAB算法,因此在标记开始时候就是一个快照。当在遍历过程中,指针的修改也还是以开始的快照原始引用为准,因此,当遍历过程中修改的指针指向可能导致浮动垃圾,这些垃圾要到下一次的gc中消除。

G1 SATB和Incremental Update算法的理解

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的工作量。

Java Hotspot G1 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

JDK8新垃圾回收机制--G1垃圾回收机制 Rset的概念图解。同时,hugo对象是要求连续的region以进行分配的。

三色标记

面试官:你说你熟悉jvm?那你讲一下并发的可达性分析

可能是最全面的G1学习笔记(

按照R大的说法:CMS的incremental update设计使得它在remark阶段必须重新扫描所有线程栈和整个young gen作为root;G1的SATB设计在remark阶段则只需要扫描剩下的satb_mark_queue。)

CMS和G1的回收过程

面试官问我G1回收器怎么知道你是什么时候的垃圾

JVM调优:CardTable简介(

\Java虚拟机用了一个叫做CardTable(卡表)的数据结构来标记老年代的某一块内存区域中的对象是否持有新生代对象的引用,卡表的数量取决于老年代的大小和每张卡对应的内存大小,每张卡在卡表中对应一个比特位,当老年代中的某个对象持有了新生代对象的引用时,JVM就把这个对象对应的Card所在的位置标记为dirty(bit位设置为1),这样在Minor GC时就不用扫描整个老年代,而是扫描Card为Dirty对应的那些内存区域。

)

 

 

一文带你深入理解JVM - ZGC垃圾收集器(以上无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。CMS是基于增量更新来做并发标记的,G1、Shenandoah则是用原始快照来实现。

 

  • 小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。
  • 中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。·
  • 大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象,最小容量可低至4MB,所有大型Region可能小于中型Region。大型Region在ZGC的实现中是不会被重分配的,因为复制一个大对象的代价非常高昂。

流程:

  • 并发标记(Concurrent Mark):与G1、Shenandoah一样,并发标记是遍历对象图做可达性分析的阶段,它的初始标记和最终标记也会出现短暂的停顿,整个标记阶段只会更新染色指针中的Marked 0、Marked 1标志位。
  • 并发预备重分配(Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。
  • 并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。
ZGC的染色指针因为“自愈”(Self-Healing)能力,所以只有第一次访问旧对象会变慢,而Shenandoah的Brooks转发指针是每次都会变慢。 一旦重分配集中某个Region的存活对象都复制完毕后,这个Region就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉,因为可能还有访问在使用这个转发表。
  • 并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,但是ZGC中对象引用存在“自愈”功能,所以这个重映射操作并不是很迫切。ZGC很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节省了一次遍历对象图的开销。

ZGC存在的问题

ZGC最大的问题是浮动垃圾。

 

Shenandoah GC:一个来自JDK12的全新并发压缩垃圾回收器(

Shenandoah的停顿时间和堆的大小没有任何关系,这就意味着无论你的堆是200MB,2GB还是200GB,停顿时间是一样的。

Shenandoah是一个基于Region设计的垃圾收集器,这点和G1类似,它把整个堆当作Region集合来维护。但是,Shenandoah不需要remember set或者card table来记录跨region引用。 
其中一个原因是(无条件)card mark可能引起false sharing,而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

深入理解JVM - Shenandoah垃圾收集器(

Brooks 在原有对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己(类似句柄,一个是放在句柄池中,一个是放在对象头前面),如图:

再谈java的gc算法_第1张图片

在对象移动的时候我们只需要将Brooks Pointer 指向新对象,在对象访问过程中,只通一条mov指令就可以完成对新对象的访问了,如图:

再谈java的gc算法_第2张图片

当写操作发生时,Shenandoah收集器是通过CAS(Compare And Swap)操作,来保证收集器线程或者用户线程只有其中之一可以进行修改操作,以此来保证并发时对象访问的正确性。

 

 

 

 

 

 

你可能感兴趣的:(再谈java的gc算法)