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. 迁移验证清单
- 记录基线的 live set、分配率、暂停分布和 GC CPU。
- 只切换收集器,先保留简单堆参数。
- 确认目标构建、平台和监控工具支持。
- 压测稳定流量、突发流量和内存逼近上限三种情况。
- 检查 allocation stall、退化回收和容器 RSS。
- 对比端到端延迟,而不是只看 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,不能只扩大暂停目标。