vipxieliang头像
关注

GC 入门:G1 与 ZGC 怎么选

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

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--