G1之Mixed GC理论

332 阅读4分钟

Mixed GC 触发的时机

  1. 老年代占用达到阈值:当老年代的内存使用超过 -XX:InitiatingHeapOccupancyPercent 参数设定的阈值(默认值为 45%)时,G1 会启动并发标记周期。
  2. 并发标记周期完成:并发标记周期结束后,G1 会根据标记结果,选择回收收益高的老年代区域。
  3. 年轻代 GC 后的评估:在一次年轻代 GC 之后,如果回收集合选择器中待回收的总字节数超过设定的阈值,G1 会将部分回收效率高的老年代区域与下一次年轻代 GC 一起回收,即执行 Mixed GC。

Mixed GC 的执行流程

并发标记阶段

1. 初始标记 - STW

  • 标记由根直接引用的对象,将可达对象的引用压入标记栈,这个过程是在YGC中完成的,不过不是每次YGC都会进行初始标记。

2. 根分区扫描 - 并发

  • 根分区集合是那些对老年代有引用的Survivor分区,标记所有从根集合可直接到达的对象并将它们的字段压入标记栈。该阶段与应用线程并发执行,必须在下一次 Young GC 之前完成。
  • 为什么根区域扫描必须在下一次 YGC 前完成?
    • 根区域扫描的目标是识别 Survivor 区域中指向老年代的引用。如果在根区域扫描尚未完成时触发了新的 YGC,Survivor 区域的内容可能会发生变化,导致部分引用关系丢失,从而影响并发标记阶段的准确性。

3. 并发标记 - 并发

  • 遍历整个堆,标记所有可达对象。中间可以发生多次 Young GC,Young GC 会中断标记过程。
  • 在并发标记阶段,G1 会根据参数 -XX:ConcGCThreads(默认 GC 线程数的1/4,即 -XX:ParallelGCThreads/4),分配并发标记线程(Concurrent Marking Threads),进行标记活动。每个并发线程一次只扫描一个分区。并发标记线程是爆发式的,在给定的时间段拼命干活,然后休息一段时间,再拼命干活。
  • 使用两种标记栈来管理对象
    • 本地标记栈:每个标记线程维护自己的本地标记栈,用于存储其扫描过程中发现的对象。
    • 全局标记栈:用于存储所有线程共享的待处理对象引用,当本地标记栈为空时,线程会从全局标记栈中获取任务。当本地标记栈满或者与其他线程共享工作时,对象会转移到全局标记栈。
  • 结束条件
    • 标记栈清空:所有本地和全局标记栈为空,表示没有待处理的对象引用。
    • SATB 队列处理完成:所有线程的 SATB 队列中的对象引用都已处理完毕,确保标记的完整性。

4. 再标记 - STW

  • 耗尽SATB缓冲区,跟踪未访问的活动对象,并执行引用处理。
  • 重新扫描所有的 GC Roots,以捕捉在并发标记期间可能新增的引用关系。
  • 为什么在此处进行引用处理?
    • 由于引用处理需要遍历整个堆并分析对象的可达性,为了保证处理的准确性和一致性,GC 选择在再标记阶段暂停应用线程,完成这项工作。

5. 清理 - 部分STW,部分并发

在这最后一个阶段,G1 垃圾收集器执行统计操作和 RSet 清理的停顿(STW)操作。在统计过程中,G1 垃圾收集器识别出完全空闲的区域以及Mixed GC回收的候选区域。当重置并将空白区域返回到空闲列表时,清理阶段部分是并发的。

Mixed GC 阶段

成功完成并发标记周期后,G1 GC从执行YGC切换到Mixed GC。

在Mixed GC 当中,G1可以选择将一些老年代region,添加到要回收的eden和survivor当中。

在G1回收了足够数量的老年代region后,G1将恢复到YGC,直到下一次并发标记周期

由于一次 Mixed GC 无法回收所有目标老年代 Region,G1 会根据设定的参数和当前的内存状况,执行多次 Mixed GC,直到达到回收目标或超过最大次数限制。