跳转至

ZGC:亚毫秒停顿的低延迟垃圾收集器#

版本信息

ZGC 解决的问题非常明确:堆很大(几十 GB~TB)时,停顿依然压在亚毫秒(<1ms)量级,且不随堆增长。这是 G1 做不到的——G1 在大堆上停顿会上升。本页讲清它靠「染色指针 + 读屏障」做到这点的原理、参数,以及从 JDK 11 到 25 的分代演进。

设计目标:停顿与堆大小无关#

ZGC 的设计目标是极致低停顿

  • 绝大部分回收与应用线程并发:标记、转移(relocation)、重映射都并发进行。
  • STW 点极短且与堆大小无关:仅初始标记、再标记等少数点是 STW,且这些点只处理 GC 根的直接引用(数量有限,不随堆增长),所以停顿恒定在亚毫秒,堆再大也不变
  • 适用超大堆:几 GB 到 TB 级堆都能保持低停顿——这是 ZGC 相对 G1 的核心优势。

工作原理:染色指针 + 读屏障#

graph LR
    App["应用线程运行"] -.并发.- C["并发标记 / 转移<br/>染色指针 + 读屏障"]
    App --> P["极短 STW<br/>初始标记 / 再标记等(与堆大小无关)"]
    C --> D["堆可达性更新<br/>对象在新位置"]
    P --> D
    style App fill:#c8e6c9
    style P fill:#ffe0b2
    style C fill:#bbdefb

两个关键技术让「并发转移」成为可能:

  • 染色指针(colored pointers):64 位指针的高位(ZGC 用其中几位)编码对象状态——Marked0/Marked1(标记轮次)、Remapped(已在新位置)等。GC 通过改写染色位跟踪每个对象处于标记/转移的哪个阶段,无需为每个对象额外建表。
  • 读屏障(load barrier):每次应用线程从堆读一个对象引用时,ZGC 插入的读屏障检查染色位——若对象待转移或引用已过期,就在应用线程里并发完成转移、更新引用、修正染色位。这让「转移对象」这个原本必须 STW 的工作,被分散到应用线程的日常读取中并发完成。

关键:转移靠读屏障在应用线程并发推进,STW 点不扫全堆,因此停顿与堆大小解耦——这是 ZGC「TB 堆仍 <1ms 停顿」的根本原因。

分代演进:从「扫全堆」到「分代」#

非分代 ZGC 虽停顿极低,但每次都扫全堆,开销随堆变大而上升(违背「弱分代假说」——多数对象朝生夕死)。分代 ZGC 引入年轻代,频繁回收小而垃圾多的年轻代、老年代低频回收,在保持低停顿时降低开销、提升吞吐

graph LR
    A["JDK 11<br/>实验(JEP 333)"] --> B["JDK 15<br/>产品级(JEP 377)"]
    B --> C["JDK 21<br/>分代可选(JEP 439)"]
    C --> D["JDK 23<br/>默认分代、弃用非分代(JEP 474)"]
    D --> E["JDK 24<br/>移除非分代(JEP 490)"]
    style A fill:#fff9c4
    style E fill:#c8e6c9

JVM 配置参数#

启用#

# JDK 15+ 产品级,直接用
java -XX:+UseZGC -Xmx16g -jar app.jar

# JDK 11 实验特性,需先解锁
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx16g -jar app.jar

# JDK 21 显式开启分代(JDK 23+ 已默认;JDK 24/JEP 490 起非分代已移除)
java -XX:+UseZGC -XX:+ZGenerational -Xmx16g -jar app.jar

关键参数#

参数 含义 默认 / 备注
-XX:+UseZGC 启用 ZGC JDK 15+ 产品级;JDK 11 需 -XX:+UnlockExperimentalVMOptions
-XX:+ZGenerational 分代模式 JDK 21 需显式开;JDK 23+ 默认分代;JDK 24(JEP 490)起非分代已移除(-XX:-ZGenerational 无效)
-XX:SoftMaxHeapSize=<size> 软上限:超过则更积极回收 默认 = -Xmx;可设低于 -Xmx 提前回收
-XX:ZCollectionInterval=<s> 固定间隔(秒)触发 GC 0 = 不固定
-XX:ConcGCThreads=<n> 并发回收线程数 占用应用 CPU
-XX:ZUncommitDelay=<s> 空闲内存归还 OS 的延迟 控制内存归还节奏
# 典型:64GB 堆的低延迟在线服务
java -XX:+UseZGC -Xmx64g \
     -XX:SoftMaxHeapSize=56g \
     -jar service.jar

适用场景#

  • 大堆 + 对停顿敏感:几十 GB~TB 级堆、需要稳定亚毫秒停顿的在线服务(交易、广告、实时推荐)。ZGC 是这类场景的现代首选。
  • 不想因为堆大而停顿失控:G1 在几十 GB 堆上停顿会显著上升,ZGC 不会。
  • 吞吐型批处理不一定受益:ZGC 牺牲了一点吞吐换低停顿;纯 CPU 密集、不在乎停顿的批处理用 G1/Parallel 可能吞吐更高。

与前代 GC 对比:G1 / CMS → ZGC#

ZGC 把「低延迟」推到极致:相对现代默认 G1(停顿可控,但大堆上仍会上升),ZGC 用并发转移把停顿压到亚毫秒且与堆大小无关;相对前代低延迟 CMS(标记-清除、碎片化、退化长停顿),ZGC 无碎片、停顿更低。

java -XX:+UseG1GC -Xmx64g -jar app.jar   # 停顿可控,但大堆上停顿仍会上升
java -XX:+UseConcMarkSweepGC -Xmx64g -jar app.jar   # 停顿较低,但碎片化退化
java -XX:+UseZGC -Xmx64g -jar app.jar   # JDK 23+ 即分代,TB 堆仍 <1ms 停顿

常见坑 / 最佳实践#

  • ZGC ≠ 吞吐更高:它降的是停顿,不是吞吐。并发回收占应用 CPU,吞吐通常略低于 G1。按负载选:延迟敏感选 ZGC,吞吐优先选 G1/Parallel。
  • 内存开销:染色指针 + 读屏障 + 并发数据结构有额外内存开销(约堆的 15–30% 量级),规划 -Xmx 时留足余量。
  • JDK 24(JEP 490)后 -XX:-ZGenerational 失效:非分代已移除,该标志被接受但不生效;迁移脚本里清理掉即可(详见 JDK 25 JVM 与运行时)。
  • JDK 11 的 ZGC 需解锁:漏 -XX:+UnlockExperimentalVMOptions 会报 Unrecognized VM option;且部分 JDK 11 构建未含 ZGC,JDK 15+ 才普遍可用。
  • 观测:ZGC 的 GC 日志、JFR 对分析并发周期很有用;ZGC Cycles(并发周期,非 STW)与 ZGC Pauses(短 STW)两个 bean 并存,正是它「并发为主、偶发短停顿」的体现。

小结#

ZGC 把标记与转移并发化,靠染色指针 + 读屏障把 STW 压到亚毫秒且与堆大小解耦,是大堆、低延迟场景的现代首选。它从 JDK 11 实验、JDK 15 产品,一路演进到 JDK 23 默认分代、JDK 24 移除非分代(JEP 490)——分代化让它在保持低停顿的同时更省开销。代价是略低的吞吐与一定的内存开销,需按负载权衡。

参考#