⭐ 推荐:社区规则条款 V1.0

Java 的 G1、ZGC 和 Shenandoah 该怎么选?

满月居于空
满月居于空 版主新人站长
发布于 2026-10-13 07:43 ·1 浏览 ·0 回复
内容摘要

按 JDK 版本、堆大小、停顿目标和 CPU 核数四步选型:小堆低延迟不敏感用 Parallel,100~300ms 停顿选 G1,个位数毫秒选 ZGC 或 Shenandoah,核少优先 G1,并给出参数配置与 GC 日志验证。

学完这篇,你能按「JDK 版本 → 堆大小 → 停顿目标 → CPU 核数」四步走,在 G1、ZGC、Shenandoah 之间做出有依据的选择,并用 GC 日志验证效果。

第一步:先确认 JDK 版本,它决定哪些 GC 能用

这一步要拿到两个信息:JDK 版本号和默认 GC,后面的所有选择都建立在这上面。在服务器上执行:

java -version
java -XX:+PrintFlagsFinal -version | grep -E "UseG1GC|UseZGC|UseShenandoahGC"

可选项对照如下,这是选型的硬前提:

JDK 版本可用 GC
JDK 8只能用 G1(需 -XX:+UseG1GC 显式开启),ZGC/Shenandoah 都没有
JDK 11ZGC 作为实验特性加入,需加 -XX:+UnlockExperimentalVMOptions
JDK 15ZGC、Shenandoah 转为正式特性,不再需要 Unlock 参数
JDK 21新增分代 ZGC,需 -XX:+UnlockExperimentalVMOptions -XX:+ZGenerational
JDK 23 及以上分代 ZGC 成为默认,直接 -XX:+UseZGC 就行

注意:JDK 9 起 G1 是默认 GC,所以你什么都不加跑的就是 G1。如果你的 JDK 是 8,就不要考虑 ZGC 和 Shenandoah 了。

第二步:按堆大小和停顿目标初筛

这一步是用三个问题把范围缩到一个。按顺序问自己:

  1. 堆有多大? 堆小于 4GB 且对延迟不敏感(比如批处理),直接用 -XX:+UseParallelGC 就行,吞吐最高,不必上 G1。
  2. 能接受多长的停顿? 要求 100~300ms 级别、堆在 4GB~32GB,选 G1。要求个位数毫秒、且停顿不能随堆增长而变长,选 ZGC 或 Shenandoah。
  3. CPU 核数够吗? ZGC 和 Shenandoah 靠并发线程和应用抢 CPU,核数少于 4 时吞吐掉得明显,这时候 G1 反而更稳。

一句话总结:**G1 是分代回收,停顿可预测但随堆变大而变长;ZGC 和 Shenandoah 是并发整理,停顿基本与堆大小无关。**

第三步:G1 的参数怎么设

这一步得到一组能直接上生产、又不会调坏的 G1 参数。G1 调优只调两个值:

java -Xms16g -Xmx16g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:InitiatingHeapOccupancyPercent=45 \
     -Xlog:gc*:file=gc.log:time,uptime,level,tags \
     -jar app.jar
  • MaxGCPauseMillis=200 是停顿目标,G1 会据此调整年轻代大小。
  • InitiatingHeapOccupancyPercent=45 控制何时启动并发标记,默认 45,如果频繁出现 Full GC 可以调低到 35。

注意:不要把 -Xms 和 -Xmx 设成不一样的值。堆反复伸缩会让 G1 的停顿预测失效,这是最常见的「调了没效果」原因。

第四步:ZGC 怎么开,有哪些坑

这一步得到一条 ZGC 启动命令。JDK 17 及以上直接写:

java -Xms32g -Xmx32g \
     -XX:+UseZGC \
     -Xlog:gc*:file=gc.log:time,uptime,level,tags \
     -jar app.jar

ZGC 的目标是停顿不超过 1ms,且不随堆大小变化,TB 级堆也能扛。代价有两个:

注意:ZGC 会额外消耗 CPU(染色指针 + 读屏障),实测吞吐一般比 G1 低 5%~15%。如果你的应用是计算密集型、CPU 已经跑满 80%,上 ZGC 可能得不偿失。

注意:ZGC 在小堆(2GB 以下)上没有优势,反而因并发线程占用内存而更吃亏。堆小于 8GB 时优先考虑 G1。

第五步:Shenandoah 怎么开

这一步得到 Shenandoah 的启动命令,并确认你的 JDK 里有它。Shenandoah 由 Red Hat 主导,JDK 12 起进入 OpenJDK 主线,JDK 15 转正:

java -Xms32g -Xmx32g \
     -XX:+UseShenandoahGC \
     -Xlog:gc*:file=gc.log:time,uptime,level,tags \
     -jar app.jar

Oracle JDK 的官方构建里不含 Shenandoah。要用它,请换成 Eclipse Temurin、Amazon Corretto、Azul Zulu 或 Red Hat 的 OpenJDK 构建。先用第一步的 PrintFlagsFinal 命令确认 UseShenandoahGC 这个 flag 存在,不存在就是发行版不支持。

Shenandoah 和 ZGC 定位接近,区别是:Shenandoah 的内存开销通常更小,适合几十 GB 到几百 GB 的堆;ZGC 在超大堆上更占优。

第六步:用 GC 日志实测对比

这一步是把选型从「猜」变成「量」。用同一份压测流量,分别跑 G1 和候选 GC,然后看 gc.log:

  • 搜 Pause 行,看最大停顿(Pause Young 和 Pause Full 分开看)。
  • 看 Full GC 有没有发生,发生了几次。
  • 对比同一压测下的 QPS 和 P99 延迟。

只有 P99 延迟达标、且吞吐损失能接受的那个,才是最终答案。别只看「GC 停顿变小了」就下结论。

小结

  • JDK 8 只能用 G1;ZGC 需 JDK 11+,Shenandoah 需 JDK 12+,两者 JDK 15 起转正。
  • 堆 < 4GB 用 ParallelGC;4GB~32GB 首选 G1;要求毫秒级停顿或堆很大,才上 ZGC/Shenandoah。
  • CPU 核数少于 4,慎用 ZGC 和 Shenandoah,并发 GC 会抢走吞吐。
  • -Xms 与 -Xmx 必须相等,这条对三种 GC 都成立。
  • Oracle JDK 不含 Shenandoah,须换 Temurin、Corretto 等发行版。
  • 选型最终靠 GC 日志的停顿数据和 P99 延迟验证,不靠感觉。
版权声明:本文来自 GJ站长论坛《Java 的 G1、ZGC 和 Shenandoah 该怎么选?》
原文链接:https://www.gj0.com/thread-1811.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~