跳到主要内容

ZGC、Shenandoah 与低延迟代价

ZGC 和 Shenandoah 都把大量标记与对象重定位工作放到应用运行期间,以缩短随堆增大而扩张的停顿。它们仍需要 CPU、内存余量和访问屏障,也不能在并发回收跟不上分配时保证延迟。

1. 低停顿收集器把工作移到并发阶段

传统疏散收集在 STW 中复制存活对象,停顿容易随 live set 增长。低延迟收集器通过读/加载屏障、转发表或带颜色的引用等机制,让应用线程可以在对象移动期间继续访问正确位置。

这个变化没有消除工作量,而是改变了执行时间和承担者:

  • 应用的引用访问路径多了屏障。
  • GC 并发线程持续占用 CPU 和内存带宽。
  • 堆要保留回收和重定位余量。
  • 某些阶段仍需短暂停顿完成根处理或全局切换。

2. 现代 ZGC 使用分代模式

JDK 23 将 Generational ZGC 设为默认,JDK 24 移除了非分代 ZGC。现代 JDK 上启用:

java -XX:+UseZGC -jar app.jar

不需要再添加旧文章中的 -XX:+ZGenerational。JDK 版本较旧时,参数和能力不同,应查看对应版本文档。

ZGC 使用带元数据的引用和 load barriers 等机制支持并发标记与重定位。其目标是很低的最大暂停,同时支持从较小到很大的堆;实际业务延迟仍受 CPU 饱和、缺页、应用锁和 I/O 影响。

3. Shenandoah 也并发压缩堆

Shenandoah 的目标同样是在应用运行时并发移动对象,通过引用屏障保证访问正确。它由 OpenJDK 社区开发,但是否随 JDK 发行版提供取决于供应商和平台;Oracle JDK 的可用收集器列表与某些 OpenJDK 发行版并不相同。

Generational Shenandoah 在 JDK 25 交付。具体版本中是否默认、是否需要 ShenandoahGCMode 以及支持状态,都应以所用发行版启动帮助和发行说明为准。

java -XX:+UseShenandoahGC -version

如果 JVM 报 Unrecognized VM option,说明当前构建没有提供它,不能只凭 Java 主版本判断。

4. 并发回收需要分配余量

设应用分配率为 A,一次并发周期需要时间 T。周期进行期间至少还会产生约 A × T 的新分配,再加上重定位与波动余量。若可用堆不足,应用可能等待 GC 提供空间,或收集器进入退化/更重路径。

因此低延迟配置常需要:

  • 最大堆明显高于稳定 live set。
  • 足够 GC 并发线程与 CPU 配额。
  • 控制突发分配和巨型对象。
  • 让并发周期及时启动。

把堆刚好设到 live set 上方,会让并发优势失去运行空间。

5. 吞吐、CPU 和内存是主要交换条件

与 G1 或 Parallel 相比,低延迟收集器可能:

  • 降低长尾 GC 停顿。
  • 增加引用屏障和并发 GC 的 CPU。
  • 需要更多堆余量。
  • 改变对象布局、压缩指针或可用平台条件。

这不是固定结论,版本优化会改变差距。选择时测量应用吞吐、P99/P999、GC CPU、RSS 和分配停滞,不只引用收集器宣传的最大暂停。

6. G1、ZGC 与 Shenandoah 的选择

可从目标出发:

  • G1:默认通用起点,在吞吐和数百毫秒以内暂停目标间平衡。
  • ZGC:JDK 自带的低延迟选择,适合严格暂停目标或大堆,接受并发成本。
  • Shenandoah:供应商构建提供时的另一低延迟选择,需要验证版本与平台支持。
  • Parallel:批处理吞吐优先且能够容忍 STW。

先在相同 JDK、相同容器限制和相同请求回放下比较。切换收集器后应清理旧收集器专属参数,避免无效或冲突配置。

7. 迁移验证清单

  1. 记录基线的 live set、分配率、暂停分布和 GC CPU。
  2. 只切换收集器,先保留简单堆参数。
  3. 确认目标构建、平台和监控工具支持。
  4. 压测稳定流量、突发流量和内存逼近上限三种情况。
  5. 检查 allocation stall、退化回收和容器 RSS。
  6. 对比端到端延迟,而不是只看 GC pause。

8. 常见问题

8.1 ZGC 的暂停是否与堆大小完全无关

目标是让主要耗时工作并发执行,使暂停不随堆大小线性增长;但根集合、线程数量、系统调度和实现细节仍会影响暂停。不能把它理解为数学上的完全无关或实时上限。

8.2 使用 ZGC 后是否不需要调堆

仍需要为 live set、分配突发和并发周期预留容量,并把 RSS 纳入容器预算。堆太小会产生分配停滞,太大也可能占用不必要资源。

8.3 Shenandoah 和 ZGC 哪个一定更快

没有跨工作负载答案。JDK 版本、对象图、分配率、CPU、堆大小和发行版实现都会影响结果,必须在目标环境比较。

9. 面试题

9.1 G1 和 ZGC 的主要差异是什么,为什么不少服务仍使用 G1

出现公司:某云原生企业

考察重点

  • G1 在 STW 中疏散 collection set,ZGC 并发重定位更多。
  • 低延迟用 CPU、屏障和内存余量交换。
  • 默认成熟度、目标延迟和实测结果决定选择。

相关内容:第 1 节“低停顿收集器把工作移到并发阶段”至第 6 节“G1、ZGC 与 Shenandoah 的选择”。

参考回答

G1 并发完成全局标记,但存活对象疏散主要发生在 STW collection set 中;ZGC 把对象重定位等更多工作放到并发阶段,靠引用屏障维持访问,因此能把 GC 停顿压得更低。

代价是持续的屏障、GC CPU 和更大的堆余量。若服务的暂停目标用 G1 已经满足,吞吐和运维经验更重要,就没有必要只为“更新”切换。应在同环境比较端到端长尾、吞吐和 RSS。

9.2 低延迟收集器为什么仍可能发生分配停滞

出现公司:阿里云

考察重点

  • 并发周期期间应用仍在分配。
  • live set、分配率、周期时间与堆余量的关系。
  • CPU 不足会让收集器落后。

相关内容:第 4 节“并发回收需要分配余量”和第 5 节“吞吐、CPU 和内存是主要交换条件”。

参考回答

并发回收不停止应用分配。如果 live set 很大、分配速率高,而 GC 因 CPU 不足或启动太晚无法及时完成,剩余堆空间会先被耗尽,分配线程只能等待收集器提供空间,甚至进入退化路径。

因此要为一个并发周期内的新分配和重定位留余量,并同时看 GC CPU、周期时长和 allocation stall,不能只扩大暂停目标。