Administrator
发布于 2023-04-26 / 1755 阅读
29

JDK 21 生产环境 GC 选型:G1、ZGC、Shenandoah 实测

背景:为下半年的 JDK 21 LTS 提前做验证

四月我们开始评估下半年升级到 JDK 21 LTS 的可行性。GC 是这次升级最敏感的部分——ZGC 在 JDK 21 里终于要兑现「亚毫秒停顿」的承诺,Shenandoah 也迭代到第三代。我拉了 JDK 21 的早期 EA build(当时到 build 19 阶段),在一台 8C16G 的压测机上,用真实订单服务的堆快照,把 G1、ZGC、Shenandoah 三种收集器跑了一遍对比。先说清楚:以下数据是 EA 阶段的预研,不是 GA 后的生产结论。

测试场景与配置

堆固定 12GB,用 JMeter 模拟下单混合负载(读写比 7:3),持续 30 分钟,观察吞吐、停顿、内存开销:

# G1(JDK 21 默认)
-XX:+UseG1GC -Xmx12g -Xms12g

# ZGC
-XX:+UseZGC -Xmx12g -Xms12g

# Shenandoah
-XX:+UseShenandoahGC -Xmx12g -Xms12g

吞吐对比:G1 仍是最稳的

收集器吞吐(事务/秒)最大停顿平均停顿常驻内存开销
G141800210ms28ms12.0GB
ZGC389001.4ms0.6ms14.8GB
Shenandoah401003.2ms1.1ms13.6GB

ZGC 吞吐比 G1 低约 7%,Shenandoah 低约 4%。代价换来了数量级的停顿下降。G1 在 12GB 堆下最大停顿仍有 210ms,是 Promotion Failure 触发的 Full GC 边缘。

延迟:低延迟场景 ZGC 完胜

我们的支付回调链路对 P99 敏感。ZGC 的 P99 停顿稳定在 1.4ms 以内,Shenandoah 差不多量级。G1 虽然平均 28ms,但偶发会冲到 200ms+,在延迟敏感的接口上会直接变成超时。

ZGC 的秘密是染色指针 + 读屏障,几乎所有回收工作都和应用线程并发,不依赖分代。Shenandoah 用 Brooks 指针 + 写屏障,原理不同但目标一致。

内存开销的隐性成本

ZGC 为了染色指针要预留一部分地址空间,且需要更多内存来容纳并发阶段的浮动垃圾,实际常驻比 G1 多 2~3GB。12GB 堆场景下差异不明显,但如果是 64GB 大堆,ZGC 的额外开销会更可观。Shenandoah 居中。

选型结论

  • 堆 < 16GB、吞吐优先、能容忍百毫秒级停顿:G1 足够,且最省心。
  • 低延迟交易链路、堆中等:ZGC 的亚毫秒停顿值得那 7% 吞吐损失,前提是机器内存有裕量。
  • 想折中、又不想给 ZGC 那么多内存:Shenandoah 是稳妥选择。

写在后面

现在回头看,《JDK 21 生产环境 GC 选型:G1、ZGC、Shenandoah 实测》本身不算多难,难的是线上真出问题那十分钟里的判断。经验都是这么来的。

参考