Java Garbage First(G1)垃圾回收器介绍及与CMS对比

在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。在堆的结构设计时,G1打破了以往将收集范围固定在新生代或老年代的模式,G1将堆分成许多相同大小的区域单元,每个单元称为Region,Region是一块地址连续的内存空间,并且新生代和老年代的大小也不是固定的了可以根据需要扮演eden suvivor 或者 old。还有一个humongous区存储大对象。G1模块的组成如下图所示:

Java Garbage First(G1)垃圾回收器介绍及与CMS对比_第1张图片

G1收集器将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。Region的大小是一致的,数值是在1M到32M字节之间的一个2的幂值数,JVM会尽量划分2048个左右、同等大小的Region。

G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1会通过一个合理的计算模型,计算出每个Region的收集成本并量化,这样一来,收集器在给定了“停顿”时间限制的情况下,总是能选择一组恰当的Regions作为收集目标,让其收集开销满足这个限制条件,以此达到实时收集的G1收集的运作过程大致如下:

  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短
  • 并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。
  • 最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行
  • 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。

Java Garbage First(G1)垃圾回收器介绍及与CMS对比_第2张图片

G1中不仅采用了Card Table,还使用了remember set用来跟踪对象引用,避免扫描所有Region。

下图展示的是RSet与Card的关系。简单来说Region由多个Card组成,且每个Region初始化时,会初始化一个remembered set,用来记录其他Region指向自己对象的引用。同样的,JVM会截获引用类型数据的写操作,每次写操作会产生一个写屏障,中断并检查引用的对象Card是否存在于不同的region中,并更新被引用对象的RSet。所以RSet实际记录的是某个Region的某个Card。

Java Garbage First(G1)垃圾回收器介绍及与CMS对比_第3张图片

当 GC 发生时,通过 RSet 找到引用当前 Region 的 Old Regions 的某个Card进行扫描,避免了扫描全部的 Old Regions,提高扫描效率。

RSet和Card其实解决了不同维度的问题:前者解决回收老年代时需要扫描所有Old Region的问题,后者解决回收新生代出现跨代引用时需要扫描全部老年代的问题。可以把Card Table理解成内存分页的标识位,RSet理解成记录内存页关系的链表

对比CMS

优点:

  • 空间整合:与CMS的标记-清除算法不同,G1从整体来看是基于标记-整理算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的。但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC
  • 可预测的停顿:这是G1相对于CMS的一个优势,降低停顿时间是G1和CMS共同的关注点。
  • 更高的吞吐:G1基于标记-整理算法实现,标记整理算法需要移动对象,因此GC的延时较长,但对象分配的效率高;CMS基于的标记-清除算法不需要移动对象但会产生大量不连续的内存空间,GC的延时较短,但对象分配的时间较长。因此G1的垃圾回收吞吐更高,CMS的延时更短。

缺点:

  • 卡表的实现更为复杂,占用10%~20%的内存

通常来说小内存适合使用CMS,大内存适合使用G1,堆内存的平衡点在6GB~8GB

参考

https://juejin.cn/post/6844903974676463629

《深入理解java虚拟机 第3版》

你可能感兴趣的:(java,开发语言)