1. 引言
垃圾回收(Garbage Collection,GC)是 JVM 内存管理的核心机制。对于 Java 开发者来说,理解 GC 不仅有助于排查线上内存问题,更能在不同业务场景下做出合理的技术选型。随着 JDK 版本的演进,G1(Garbage First)和 ZGC(ZGC Garbage Collector)逐渐成为主流选择。本文将从分代回收、三色标记、卡表等基础概念讲起,分析 G1 与 ZGC 的适用场景与低延迟特点,并给出常用 GC 参数与日志分析方法,帮助你快速上手 GC 选型。
2. 分代回收
分代回收(Generational Collection)基于「大多数对象朝生夕灭」的弱分代假设,将堆内存划分为年轻代(Young Generation)和老年代(Old Generation)。
- 年轻代:存放新创建的对象,又分为 Eden 区和两个 Survivor 区(S0、S1)。对象在 Eden 区分配,经历 Minor GC 后存活对象被复制到 Survivor 区,多次存活后晋升到老年代。
- 老年代:存放长期存活的对象,触发 Major GC / Full GC 的频率较低,但回收成本更高。
分代回收的核心收益在于:将回收频率高、存活率低的年轻代与回收频率低、存活率高的老年代分开处理,从而用更小的停顿时间换取更高的吞吐量。
3. 三色标记
三色标记(Tri-color Marking)是并发标记阶段的基础算法,用于解决「标记过程中对象引用关系发生变化」的问题。它将对象分为三种颜色:
- 白色:尚未被访问到的对象,标记结束后仍为白色的对象将被回收。
- 灰色:自身已被标记,但其引用的对象尚未全部标记完成。
- 黑色:自身及其直接引用的对象都已被标记完成。
标记过程从 GC Roots 出发,将可达对象依次由白变灰、由灰变黑。并发标记期间,如果黑色对象新增了对白色对象的引用,就可能出现「漏标」,导致存活对象被错误回收。为此,G1 使用 SATB(Snapshot-At-The-Beginning)快照,ZGC 则通过读屏障(Load Barrier)与染色指针(Colored Pointers)来保证正确性。
4. 卡表
卡表(Card Table)是 G1 等分代收集器用于记录「老年代对象引用年轻代对象」的数据结构。年轻代回收(Minor GC)时,GC Roots 需要扫描老年代,若全量扫描代价过高,因此引入卡表:
- 将老年代划分为固定大小的卡(Card),每张卡对应卡表中的一个字节。
- 当老年代对象引用年轻代对象时,将该卡标记为 Dirty。
- Minor GC 时只需扫描 Dirty 卡对应的老年代区域,而非整个老年代。
卡表以空间换时间,显著降低了跨代引用的扫描成本,是分代 GC 高性能的关键设计之一。
5. G1 适用场景
G1(Garbage First)收集器将堆划分为多个大小相等的 Region,通过维护每个 Region 的回收收益优先级,优先回收「垃圾最多、收益最大」的区域,从而在可控停顿时间内完成回收。
G1 的适用场景包括:
- 中等规模堆:如 4GB 到 64GB 的堆内存,G1 能较好地平衡吞吐量与停顿时间。
- 可预测停顿目标:通过
-XX:MaxGCPauseMillis设置期望停顿时间,G1 会动态调整回收策略。 - JDK 8 及以上:JDK 9 起 G1 成为默认收集器,适合大多数服务端应用。
- 需要兼顾吞吐与延迟:相比 CMS,G1 在避免碎片化与停顿可控性上更优。
6. ZGC 低延迟特点
ZGC(ZGC Garbage Collector)是面向大堆、低延迟场景设计的并发收集器,其核心目标是将 GC 停顿时间控制在毫秒级,且不随堆大小线性增长。
ZGC 的关键技术:
- 染色指针(Colored Pointers):将对象地址的部分位用于记录标记状态,无需对象头即可完成并发标记。
- 读屏障(Load Barrier):在读取对象引用时进行状态修正,保证并发重定位期间的正确性。
- 并发整理:ZGC 的标记、转移、重定位阶段均可与业务线程并发执行,大幅降低停顿。
ZGC 适合以下场景:
- 超大堆内存:如 64GB 以上,甚至 TB 级堆,ZGC 的停顿时间依然稳定。
- 低延迟敏感业务:如在线交易、实时推荐、网关服务等,要求 GC 停顿极低。
- JDK 11+:ZGC 在 JDK 11 引入实验特性,JDK 15 起转正,JDK 17 后逐步成熟。
7. 常用 GC 参数与日志分析
7.1 常用 GC 参数
# 指定 G1 收集器
-XX:+UseG1GC
# 设置期望最大停顿时间(毫秒)
-XX:MaxGCPauseMillis=200
# 指定 ZGC 收集器
-XX:+UseZGC
# 设置堆大小
-Xms4g -Xmx4g
# 打印 GC 日志(JDK 8)
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
# 打印 GC 日志(JDK 9+)
-Xlog:gc*
7.2 GC 日志分析
以 G1 为例,一段典型的 GC 日志如下:
[GC pause (G1 Evacuation Pause) (young) 512M->128M(1024M), 0.0123456 secs]
关键信息解读:
- GC pause:停顿类型,G1 Evacuation Pause 表示年轻代转移暂停。
- 512M->128M:回收前后堆使用量。
- (1024M):当前堆总容量。
- 0.0123456 secs:本次停顿耗时。
分析建议:
- 关注停顿频率与耗时是否满足
MaxGCPauseMillis目标。 - 若年轻代回收频繁且晋升对象多,可适当调大年轻代或调整
-XX:SurvivorRatio。 - 若出现 Full GC,优先排查内存泄漏或大对象分配。
8. 总结与选型建议
G1 与 ZGC 的选择应结合业务特点:
- 默认首选 G1:大多数服务端应用、中等堆内存、可接受几十毫秒停顿,直接使用 G1 即可。
- 低延迟优先选 ZGC:超大堆、对停顿极其敏感的业务,优先考虑 ZGC。
- 关注 JDK 版本:ZGC 在较新 JDK 上表现更稳定,升级前建议做充分的压测验证。
理解分代回收、三色标记与卡表等基础原理,再结合 GC 日志持续调优,才能让 JVM 在真实业务中发挥最佳性能。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/vipxieliang/article/details/166982414



