zgc仅在jdk 17+且堆≥16gb时具实际优势,g1则在jdk 8–17、堆≤64gb场景更稳;zgc无humongous对象限制、停顿与堆大小无关,但依赖os mmap支持;监控指标不可混用,切zgc需压测p999停顿是否稳定≤1ms。

别直接比参数,先看JDK版本和堆大小——ZGC在JDK 17+、堆≥16GB时才有实际优势;G1在JDK 8–17、堆≤64GB的绝大多数服务里更稳。
用UseG1GC还是UseZGC取决于JDK大版本
OpenJDK中G1从JDK 7u4起可用,JDK 9起默认启用,JDK 17/21持续优化;ZGC在JDK 11首次以实验特性引入,JDK 17才正式稳定(-XX:+UseZGC无需加-XX:+UnlockExperimentalVMOptions),JDK 21进一步降低延迟抖动。如果你还在用JDK 8或JDK 11早期版本,UseZGC要么不可用,要么必须开启实验开关且不建议上生产。而G1在JDK 8u202之后已足够成熟,线上故障率远低于CMS退化Full GC的旧配置。
常见错误现象:Unrecognized VM option 'UseZGC' 或 Failed to enable ZGC: unsupported VM configuration,基本都是JDK版本不匹配导致。
- JDK 8 → 只能选G1(或Parallel/CMS,但CMS已在JDK 14移除)
- JDK 11–16 → ZGC需加
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC,且部分发行版(如某些Alibaba Dragonwell旧版)未集成ZGC - JDK 17+ → 直接
-XX:+UseZGC即可,但需确认OS支持(Linux x64/AArch64,Windows/macOS仅JDK 17+有限支持)
MaxGCPauseMillis对G1有效,对ZGC是伪配置
G1靠-XX:MaxGCPauseMillis=200这类目标驱动回收节奏:它会动态调整年轻代大小、Mixed GC触发时机、Region扫描数量来逼近该值——但只是“尽力而为”,超时仍会发生,尤其当老年代碎片多、Humongous对象堆积时。ZGC的停顿时间天然与堆大小无关,官方实测P99停顿MaxGCPauseMillis传给ZGC会被忽略,JVM日志里会打印Unused setting: MaxGCPauseMillis。
使用场景差异明显:
- 你有一套订单系统,P99 RT要求≤200ms,晚高峰堆占用常达50GB → G1设
MaxGCPauseMillis=100并配合G1HeapRegionSize调优,比硬上ZGC更可控 - 你做实时风控决策引擎,堆开到128GB,单次GC停顿超过5ms就会触发熔断 → ZGC是刚需,
MaxGCPauseMillis这种参数根本不用写
G1的Humongous Region和ZGC的染色指针处理大对象逻辑完全不同
G1对大对象(>Region size / 2)单独划出Humongous Region,不进Eden也不进老年代;若对象跨多个Region,必须找连续空闲Humongous Region,否则直接触发Full GC——这是很多“偶发性卡顿”的根源,比如某次上传10MB文件生成的byte[]恰好卡在Region边界。
ZGC没有Humongous概念,所有对象统一管理,靠染色指针(colored pointer)在地址位里存标记状态(Marked0/Marked1/Remapped),分配大对象只消耗虚拟内存地址空间,物理内存按需映射,完全规避了“找不到连续页”的问题。
性能影响很实际:
- G1中频繁分配1–4MB对象,且Region size设为4MB → 几乎每个都进Humongous,极易引发退化
- ZGC下同样负载,GC线程与应用线程并发标记+重定位,大对象分配几乎不增加STW负担
- 但ZGC染色指针依赖Linux
mmap的多重映射能力,容器环境下若/proc/sys/vm/max_map_count过低(如默认65536),ZGC启动会失败并报os::commit_memory failed
监控指标不能混用:G1看G1 Evacuation Pause,ZGC看ZGC Pause
G1的日志里G1 Evacuation Pause (young)和G1 Evacuation Pause (mixed)才是真正的STW阶段,而G1 Concurrent Cycle里的并发标记阶段不计入停顿——但很多团队误把整个并发周期当成“GC耗时”,导致误判。
ZGC日志干净得多:ZGC Pause (Warmup)、ZGC Pause (Allocation Rate High)、ZGC Pause (Proactive)这些才是真实STW事件,每次都在1ms内,且频率远低于G1的Evacuation次数。
容易踩的坑:
- 用Prometheus + JVM Micrometer采集
jvm_gc_pause_seconds_count时,G1的指标包含所有Evacuation,ZGC只计Pause,直接对比数值会严重高估ZGC的“GC次数” - ZGC的
ZGCCycle(完整GC周期)是后台异步运行的,不影响STW,但它的CPU占用(ZGC CPU time)可能比G1高5–10%,对CPU敏感型服务要留心 - G1的
G1OldGenerationSize和ZGC的ZHeapUsed虽都反映老年代/堆使用量,但ZGC的ZHeapUsed包含尚未重定位的旧对象,实际可用内存比G1更“乐观”
真正难的是权衡“确定性”和“未知开销”:G1的停顿虽长但可预测,ZGC的停顿虽短却依赖OS mmap行为和JDK发行版实现细节。线上切ZGC前,务必在同等流量压测中观察ZGC Pause的P999是否真稳定在1ms内——而不是只看平均值。











