Java G1 GC 技术一览

💡 原文中文,约13500字,阅读约需32分钟。
📝

内容提要

G1 GC 是 JDK 9 起的默认低延迟垃圾收集器,替代 CMS。它将堆划分为多个 Region,分代收集,通过 Remembered Set 和写屏障追踪跨代引用。核心流程包括 Young GC、并发标记、Mixed GC 和 Full GC。主要调优参数有 MaxGCPauseMillis、InitiatingHeapOccupancyPercent 等,适合大堆、低延迟场景。

🔎

延伸解读

G1 的适用边界:大堆与低延迟的权衡

文章指出 G1 适合大堆(大于等于 6G)且需要更低延迟的应用,但需要更多计算资源和内存。如果堆不够大或应用不需要低暂停,可能不适合。这提醒读者在选择 G1 前,需评估堆大小和延迟需求,避免盲目启用导致资源浪费。

调优核心:MaxGCPauseMillis 与 IHOP 的平衡

文章强调 G1 调优简单,主要设置 MaxGCPauseMillis 和 InitiatingHeapOccupancyPercent。但停顿目标设得太低可能导致堆疯狂扩容,而 IHOP 太高会延迟标记,进而引发疏散失败。读者应结合 GC 日志调整,避免过度调优破坏 G1 的自适应机制。

避免 Full GC 与疏散失败的关键点

文章建议避免 Full GC,因其单线程且慢。同时要警惕疏散失败,通常由空间不足引起,可能很快导致 Full GC。可通过增大堆、调整 G1ReservePercent 或降低 IHOP 来预防。这些措施有助于维持 G1 的稳定运行,减少长暂停。

Humongous 对象:空间浪费与碎片化风险

文章解释 Humongous 对象(大于 Region 一半)直接分配老年代,可能造成空间浪费和碎片化,甚至引发疏散失败。建议增大 Region 大小或避免大对象分配。读者应监控大对象分配,合理设置 G1HeapRegionSize 以降低风险。

❓

Q&A

G1 GC 是什么?它从哪个 JDK 版本开始成为默认垃圾收集器?

G1 GC 是 HotSpot 的低延迟垃圾收集器,全称 Garbage-First Garbage Collector,用于替代 CMS。它从 JDK 7u4 开始支持,在 JDK 9 中成为默认 GC(JEP 248)。

G1 GC 的内存布局是怎样的?Region 是如何划分的?

G1 将堆划分为多个大小相等的 Region,目标约 2028 个,可通过 -XX:G1HeapRegionSize 设置。Region 分为 Eden、Survivor、Old 和 Humongous 区域。Humongous 区域用于存放单个对象大小超过 Region 50% 的大对象。

G1 的 Young GC 流程包括哪些阶段?

G1 Young GC 是 Stop-The-World、并行、复制式的。阶段包括:Root Scanning(扫描静态和局部对象)、Update RS(更新 Remembered Set)、Process RS(处理 RS 找到存活对象)、Object Copy(拷贝存活对象到新区域)、Reference Processing(处理软、弱、虚等引用)。

G1 的 Mixed GC 是什么?它和 Young GC 有什么关系?

Mixed GC 搭载在 Young GC 之上,除了回收年轻代 Region,还会回收一部分老年代 Region。默认执行 8 次 Mixed GC(-XX:G1MixedGCCountTarget=8),Collection Set 包含部分老年代 Region、Eden 和 Survivor Region。老年代 Region 中垃圾多的优先回收。

G1 有哪些主要调优参数?如何设置?

主要参数包括:-XX:MaxGCPauseMillis 设置最大 GC 暂停时间目标(默认 200ms);-XX:InitiatingHeapOccupancyPercent 设置触发并发标记周期的堆占用率阈值(默认 45%);-XX:G1HeapRegionSize 设置 Region 大小;-XX:G1MixedGCCountTarget 设置 Mixed GC 目标次数;-XX:G1HeapWastePercent 设置可浪费堆百分比。

G1 适合在什么场景下使用?什么时候不该用 G1?

G1 适合大堆(大于等于 6G)且需要低延迟的应用。如果堆不够大(低于 6G)或应用不需要低暂停,则不适合使用 G1,因为 G1 需要更多计算资源和内存来运行。

G1 的 Remembered Set 和写屏障是做什么的?

Remembered Set 记录其他 Region 指向本 Region 的引用,用于追踪跨代引用,避免回收时扫描全堆。写屏障是一小段代码,在每次引用赋值时执行,将更新记录到 card 中,card 存入 dirty card queue,由 refinement 线程异步更新 RSet,以减少开销和竞争。

G1 的 Full GC 有什么问题?如何避免?

Full GC 是单线程的,非常慢。避免方法包括:避免空间耗尽(to-space exhausted),可增大最大堆或调整 Region 大小;避免过多 Humongous 分配;避免过长的引用处理,可开启 -XX:+ParallelRefProcEnabled。

🏷️

标签

➡️

继续阅读