Mixed GC 触发的时机
- 老年代占用达到阈值:当老年代的内存使用超过
-XX:InitiatingHeapOccupancyPercent参数设定的阈值(默认值为 45%)时,G1 会启动并发标记周期。 - 并发标记周期完成:并发标记周期结束后,G1 会根据标记结果,选择回收收益高的老年代区域。
- 年轻代 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,直到达到回收目标或超过最大次数限制。