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 参数

bash 复制代码
# 指定 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 日志如下:

text 复制代码
[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 在真实业务中发挥最佳性能。

相关推荐
兔小盈1 小时前
有效三角形个数与和为s的两个数字
算法·双指针
Cc.Y5 小时前
Java零基础入门:字符串深度掌握——从基础API到StringBuilder性能优化
java·开发语言·性能优化
shuoshuohaohao10 小时前
MyBatis 小知识点
java·spring·mybatis
海上小飞龙10 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
青少儿编程课堂10 小时前
后缀自动机解析:本质不同子串与最长重复片段统计
c++·python·算法·bfs·信息学竞赛
朝朝辞暮i10 小时前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
谢亮_vipxieliang11 小时前
泛型擦除、通配符与 PECS 一次讲透
java·面试
八解毒剂11 小时前
【计组】中央处理器CPU
java·开发语言·计算机组成原理
乐迪信息11 小时前
港口船舶逆行怎么管?AI防爆摄像机搭载智能检测算法
大数据·人工智能·深度学习·算法·计算机视觉