JDK JDK 学习手册8 → 27 · JDK 专用学习站
DEEPDIVE · RUNTIME · JEP 377 / 379

JDK 17 的 GC:G1 默认,ZGC 与 Shenandoah 转正

JDK 17 · 深钻主题 03 / 03 · 预计 45 分钟
进阶四步深钻JEP 377JEP 379
一句话JDK 17 默认 G1;ZGC 与 Shenandoah 都在 15 转正,17 可以直接用于生产。三者的差别不是「谁更新」,而是停顿目标、吞吐与内存占用之间的取舍。
本质:GC 选择是在三个量之间取舍:停顿时间、应用吞吐、内存与 CPU 开销。没有一种收集器能同时把三者做到最好;堆越大、停顿要求越硬,并发收集器的代价越明显。

学完你要能自己回答

  1. G1 的 Region、Remembered Set 与 Mixed GC 分别解决什么问题?
  2. ZGC 的染色指针与 load barrier 如何把停顿压到毫秒级?代价是什么?
  3. Shenandoah 与 ZGC 的实现路线有什么不同?
  4. MaxGCPauseMillis 是 SLA 吗?设得过低会发生什么?
  5. 17 上应该怎么在 G1 / ZGC / Shenandoah 之间做选择?
01 三种收集器的位置02 停顿目标不是 SLA

三种收集器的位置01

JDK 17 提供 Serial、Parallel、G1、ZGC、Shenandoah 和 Epsilon。默认是 G1;ZGC 与 Shenandoah 在 15 从实验转为正式,17 不再需要解锁参数。

收集器17 状态核心目标典型代价
G1默认可预测的停顿 + 较好吞吐Remembered Set 与写屏障开销
ZGC15 正式极低停顿,支持大堆染色指针、load barrier、内存占用
Shenandoah15 正式低停顿,并发回收Brooks 指针、读屏障、额外对象头
Parallel可用吞吐优先停顿随堆增长
Serial可用小堆、单核全停顿,不适合大堆服务

「低停顿」不等于「更快」。并发收集器把工作放到应用线程之外,会消耗 CPU 和内存带宽;停顿缩短了,单位时间内的吞吐可能下降。选择收集器之前,先明确你优化的是尾延迟还是吞吐。

JDK 17 的 ZGC 是单代收集器,没有分代模式;分代 ZGC(JEP 439)在 21 才出现。不要拿 21 的 ZGC 参数与行为套到 17。

停顿目标不是 SLA02

G1 的 -XX:MaxGCPauseMillis 默认 200ms(本机 JDK 17 实测)。它是一个目标,不是硬性上限;G1 会据此调整年轻代大小和回收频率,但无法保证每次停顿都不超过它。

  • 把目标设得过低,G1 会缩小年轻代、增加 GC 次数,吞吐可能明显下降。
  • 堆很大、对象分配率很高时,单次停顿仍可能超过目标。
  • ZGC / Shenandoah 的低停顿来自并发标记与并发重定位,不代表应用线程完全不受影响。
  • 评估 GC 要看延迟分位(p95 / p99)、GC 频率、CPU 占用和分配率,不只看平均值。
先猜再看把 MaxGCPauseMillis 设成 10ms,服务延迟就会变好吗?
不一定。更低的暂停目标通常意味着更小的年轻代、更频繁的 GC,甚至更多 Full GC 风险;吞吐下降会把延迟从 GC 转移到排队。要用压测和 GC 日志验证。
没有「最佳 GC 参数」。同一组参数在不同堆大小、分配率、对象生命周期下表现不同;先测量,再调参。
01 G1:区域化与增量回收02 ZGC:染色指针与并发重定位03 Shenandoah:Brooks 指针

G1:区域化与增量回收01

G1 把堆划分成大小相等的 Region(1–32MB,按堆大小取 2 的幂)。对象分配在 Region 中,年轻代只是 Region 的一个逻辑集合,不再要求物理连续。

  1. Young GC:回收 Eden 与 Survivor Region,存活对象复制到新的 Survivor 或晋升老年代。
  2. 并发标记:老年代占用达到阈值后启动,使用 SATB(Snapshot-At-The-Beginning)记录并发期间的引用变化。
  3. Mixed GC:在年轻代回收之外,挑选垃圾比例高的老年代 Region 一起回收。
  4. Full GC:并发标记或复制失败时的兜底,通常是全停顿,应尽量避免。

跨 Region 引用由 Remembered Set 记录,避免每次 Young GC 扫描整个老年代。对象大于 Region 一半时按 Humongous 处理,分配在连续的 Humongous Region 中;大数组、大字符串会放大这种开销。

概念解决的问题代价
Region把堆切成可增量回收的单元大对象需要连续 Region
Remembered Set只扫描有跨代引用的 Region写屏障与内存开销
SATB并发标记期间保持快照语义标记阶段额外 CPU
Mixed GC逐步回收老年代,避免全堆停顿回收选择与复制成本
G1 不是「永远不 Full GC」。分配过快、Humongous 对象过多、并发标记跟不上时,仍可能发生 Full GC。GC 日志里看到 Full GC,要优先查分配率和大对象。

ZGC:染色指针与并发重定位02

ZGC 把标记信息放在指针里(染色指针),而不是对象头。应用线程读取引用时经过 load barrier,根据指针颜色决定是否需要帮忙完成标记或重定位。

  1. 并发标记:遍历对象图,把可达性信息写进指针的颜色位。
  2. 并发重定位:把存活对象复制到新 Region,旧地址通过转发表记录。
  3. 并发重映射:修正仍指向旧地址的引用,使旧空间可回收。
  4. 整个过程只有极短的停顿用于根扫描等关键步骤,大部分工作与应用线程并发。
  • ZGC 在 17 是单代收集器:没有年轻代/老年代之分,所有周期都针对整个堆。
  • 染色指针依赖 64 位地址空间;ZGC 在 17 不支持压缩普通对象指针,引用占用更多内存。
  • load barrier 让每次引用读取都多了一点开销;低停顿的代价是吞吐与内存。
  • JEP 377 的目标是把停顿控制在 10ms 以内,但这是设计目标,不是任何负载下的保证。
ZGC 适合「堆很大、尾延迟要求硬」的服务;如果堆只有几个 GB、吞吐优先,G1 往往更划算。不要因为 ZGC 是「新一代」就默认它更快。

Shenandoah:Brooks 指针03

Shenandoah 也做并发标记与并发 evacuation,但它用 Brooks 指针实现对象转发:每个对象多一个转发指针字,读屏障在访问对象时检查是否已被移动。

维度ZGCShenandoah
标记信息位置引用指针的颜色位对象头 / Brooks 指针
读屏障load barrier 检查指针颜色读屏障检查转发指针
压缩指针17 不支持17 支持(取决于平台与堆)
分代17 单代17 单代
主要代价地址空间与内存占用每对象额外指针、读屏障开销

两者都追求低停顿,但实现路线不同:ZGC 把元数据放进指针,Shenandoah 把转发状态放进对象。选择时不能只看纸面参数,要看供应商支持、堆规模、分配模式和实测尾延迟。

Shenandoah 在 Corretto 17 上可直接用 -XX:+UseShenandoahGC 启动(本机验证)。但是否可用取决于发行版与平台,部署前先在目标镜像里确认。
01 选择表02 参数与验证03 排障顺序

选择表01

场景优先选择理由
通用 Web 服务、堆 < 16GBG1(默认)吞吐与停顿平衡,调优资料多
堆很大、尾延迟要求硬ZGC并发回收,停顿与堆大小关系弱
低停顿 + 希望保留压缩指针ShenandoahBrooks 指针路线,支持压缩指针
批处理、吞吐优先Parallel减少并发开销,充分利用 CPU
小堆、单核、启动快Serial实现简单,开销小
极端低延迟演示 / 测试Epsilon不回收,仅分配;不适合生产常驻

这张表只是起点。真正选择前,用生产相似的堆大小、分配率和对象生命周期做压测,比较 p99 延迟、吞吐、GC CPU 占用和内存足迹。

不要同时开多个收集器参数。-XX:+UseG1GC -XX:+UseZGC 会让 JVM 报冲突;一次只选一个。

参数与验证02

# 默认:G1
java -Xmx256m -cp out GcInfo
# maxHeapMB=256
# collector=G1 Young Generation
# collector=G1 Old Generation

# ZGC(17 单代)
java -XX:+UseZGC -Xmx256m -cp out GcInfo
# maxHeapMB=256
# collector=ZGC Cycles
# collector=ZGC Pauses

# Shenandoah
java -XX:+UseShenandoahGC -Xmx256m -cp out GcInfo
# maxHeapMB=256
# collector=Shenandoah Pauses
# collector=Shenandoah Cycles
$ java -XX:+PrintFlagsFinal -version | grep -E "UseG1GC|MaxGCPauseMillis"
    uintx MaxGCPauseMillis                         = 200                                       {product} {default}
     bool UseG1GC                                  = true                                      {product} {ergonomic}
  • 用 ManagementFactory.getGarbageCollectorMXBeans() 或 GC 日志确认实际生效的收集器,不要只看启动参数。
  • -Xlog:gc*:file=gc.log:time,uptime,level,tags 是 17 推荐的日志写法,替代已弃用的 PrintGCDetails。
  • -Xms 与 -Xmx 设成相同值可以避免堆伸缩带来的额外停顿,但是否合适取决于部署密度。
  • ZGC / Shenandoah 的并发线程会占用 CPU;容器里要设置合理的 CPU limit,避免 GC 线程与应用线程互相挤压。
ZGC 在 17 不支持压缩指针,堆外内存与元空间之外的对象引用占用更高。相同 -Xmx 下,ZGC 的实际内存足迹通常大于 G1;容量规划要留余量。

排障顺序03

  1. 先看 GC 日志:是 Young GC 频繁、Mixed GC 太慢,还是出现了 Full GC。
  2. 看分配率与晋升率:年轻代过小会导致对象过早晋升,老年代压力增大。
  3. 查大对象:Humongous 分配、超大数组、缓存无上限都会让 G1 退化。
  4. 查容器限制:堆设置超过 cgroup 限制会被 OOM Killer 杀掉,不是 GC 问题。
  5. 需要低延迟再评估 ZGC / Shenandoah;先确认瓶颈真的在 GC 停顿。
  6. 调整后重新压测,比较 p99、吞吐和 CPU,不要只看单次 GC 时间。
现象可能原因先查什么
Full GC 频繁老年代增长快、并发标记跟不上分配率、晋升率、老年代占用曲线
Young GC 很频繁年轻代过小、分配率过高Eden 大小、对象分配热点
单次停顿很长Mixed GC 回收 Region 过多、HumongousMixed GC 日志、大对象
容器被 kill堆 + 元空间 + 线程栈超过 limitRSS、cgroup 限制、-Xmx
吞吐下降但停顿短并发收集器占用 CPUGC 线程 CPU、应用 CPU
GC 调优的最后一步永远是回到应用:减少无谓分配、控制缓存大小、避免大对象,通常比换收集器收益更大。
01 先猜再看02 费曼自检:能否讲清因果

先猜再看01

先猜再看ZGC 的停顿比 G1 短,所以吞吐一定更高吗?
不一定。ZGC 用并发线程完成标记和重定位,会消耗 CPU 与内存带宽;load barrier 也给引用读取增加开销。低停顿的代价可能是吞吐下降,必须实测。
先猜再看MaxGCPauseMillis=200 是「每次停顿不超过 200ms」的保证吗?
不是。它是 G1 的调优目标,G1 会据此调整回收策略,但无法保证每次停顿都达标。Full GC、Humongous 分配和超大存活集都可能突破目标。
先猜再看17 的 ZGC 有分代模式吗?
没有。17 的 ZGC 是单代收集器,所有周期针对整个堆;分代 ZGC 在 21(JEP 439)才出现。不要把 21 的 ZGC 行为写进 17 的调优方案。

费曼自检:能否讲清因果02

01. G1 的 Remembered Set 解决什么问题?
它记录跨 Region 的引用,让 Young GC 只扫描有跨代引用的 Region,而不是整个老年代;代价是写屏障和额外内存。
02. ZGC 如何做到低停顿?
用染色指针在引用中保存标记状态,通过 load barrier 让应用线程参与标记/重定位,大部分标记与复制工作并发执行,只留极短停顿。
03. Shenandoah 与 ZGC 的核心实现差异是什么?
ZGC 把元数据放在指针颜色位;Shenandoah 用 Brooks 指针(对象头中的转发指针)实现并发 evacuation。两者都依赖读屏障,但内存布局和代价不同。
04. 什么时候应该坚持 G1?
大多数通用服务、堆不是特别大、没有硬性尾延迟要求时,G1 的吞吐与停顿平衡更好,调优资料也最丰富;换 ZGC 前应先证明瓶颈是 GC 停顿。
05. GC 调优应该看哪些指标?
p95/p99 延迟、GC 频率与单次停顿、应用吞吐、GC 线程 CPU、堆占用曲线、分配率与晋升率。只看平均停顿或单次 GC 时间会误导。
能把「G1 增量回收、ZGC 染色指针、Shenandoah Brooks 指针」三条路线讲清,并说明低停顿的代价,这一节就过关了。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。