摘要G1 GC 的停顿时间目标是 200ms但某游戏服务器的 P999 停顿居然达到了 2 秒——这在实时对战中是致命的。本案例记录从 CMS 迁移到 G1、再到 ZGC 的完整低延迟改造过程。核心问题在于 G1 的 Mixed GC 策略过于激进混合收集阶段扫描了过多老年代分区。通过调整 G1OldCSetRegionThresholdPercent、添加 G1MixedGCLiveThresholdPercent 限制以及最终的 ZGC 迁移将 P999 停顿从 2 秒降到 5msP9999 从 5 秒降到 20ms。一、问题背景1.1 业务场景某实时对战游戏的后端服务使用 JDK 11部署在 8 核 16GB 服务器上。游戏要求所有接口响应时间 P999 100ms否则玩家会感受到明显卡顿。1.2 故障现象监控数据GC Viewer 分析 - P50 GC 停顿~50ms可接受 - P99 GC 停顿~800ms偏高 - P999 GC 停顿~2000ms不可接受 - P9999 GC 停顿~5000ms严重 GC 日志分析 [2026-03-20T14:30:00.123] GC pause (G1 Evacuation Pause), 1856.34ms # 问题一次 GC 停顿接近 2 秒 触发原因 G1EvacuationFailure → Mixed GC → 疏散失败回收了过多分区1.3 当前配置# 原始配置JDK 11 G1-server-Xms16g-Xmx16g-XX:UseG1GC-XX:MaxGCPauseMillis200-XX:G1HeapRegionSize4m-XX:InitiatingHeapOccupancyPercent45二、问题分析2.1 G1 Mixed GC 机制回顾G1 GC 停顿时间的组成 ┌──────────────────────────────────────────────────────────────────┐ │ G1 GC 停顿时间分解 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ Young GC / Mixed GC │ │ ┌──────────┬──────────┬──────────┬──────────┐ │ │ │ Root Scan│ Update │ Object │ Update │ │ │ │ (快) │ RS │ Copy │ RS │ Relocation │ │ │ │ (并发) │ (并行) │ (并发) │ (并行) │ │ └──────────┴──────────┴──────────┴──────────┘ │ │ │ │ Mixed GC 比 Young GC 多出的部分 │ │ - 扫描更多分区老年代分区 │ │ - CSet 选择更复杂 │ │ - 可能触发 Evacuation Failure疏散失败 │ │ │ └──────────────────────────────────────────────────────────────────┘2.2 GC 日志详细分析# 详细 GC 日志分析 [2026-03-20T14:30:00.1230800: 12345.678: [GC pause (G1 Evacuation Pause) (mixed) # 说明这是 Mixed GC混合 GC # 外部根处理 [External root processing (outside JVM): 0.5 ms] # 扫描 Region Region 列表 [Scan TAMS: 0.1 ms] # 根分区扫描 [Root Region Scan: 123.4 ms] ← 异常高 # 对象复制 [Object Copy (撤出)]: 1456.2 ms] ← 最大的耗时点 # 清理 [Clear CT: 0.2 ms] [Other: 12.3 ms] # 疏散失败标志 [Evacuation Failure: 2345.6 ms] ← 疏散失败 ]2.3 根因定位根因分析 ┌──────────────────────────────────────────────────────────────────┐ │ │ │ 问题链条 │ │ │ │ 1. G1HeapRegionSize4MB → 堆被分成 4000 个 Region │ │ 2. Mixed GC 选择了过多分区进入 CSetCollection Set │ │ 3. 疏散过程中对象复制超时每对象复制时间过长 │ │ 4. 疏散失败 → G1 将分区标记为 Humongous → 跳过回收 │ │ 5. 老年代碎片化加剧 → 下一轮 Mixed GC 更激进 │ │ │ │ 关键参数问题 │ │ - G1OldCSetRegionThresholdPercent 默认 10%太多分区 │ │ - G1MixedGCLiveThresholdPercent 默认 85%太低 │ │ │ └──────────────────────────────────────────────────────────────────┘三、解决方案3.1 方案一G1 调优保守优化# 调优后的 G1 配置-server-Xms16g-Xmx16g-XX:UseG1GC-XX:MaxGCPauseMillis200-XX:G1HeapRegionSize8m# 增大 Region减少分区数量-XX:InitiatingHeapOccupancyPercent60# 提高触发阈值-XX:G1OldCSetRegionThresholdPercent5# 减少每次 Mixed GC 的分区数-XX:G1MixedGCLiveThresholdPercent95# 只回收存活率 95% 的分区-XX:G1HeapWastePercent20# 允许更多内存浪费3.2 方案二ZGC 迁移激进优化# ZGC 配置JDK 11-server-Xms16g-Xmx16g-XX:UseZGC-XX:ConcGCThreads4# 并发 GC 线程-XX:UnlockExperimentalVMOptions-XX:ZCollectionInterval300# 最小 GC 间隔 5 分钟-XX:ZProactivetrue# 主动 GC预防性3.3 参数对比┌──────────────────────────────────────────────────────────────────┐ │ G1 调优 vs ZGC 迁移对比 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 指标 │ G1 调优 │ ZGC 迁移 │ │ │ ─────────────────┼──────────────┼───────────────┼──────────── │ │ P999 停顿 │ 500ms │ 5ms │ 99% ↓ │ │ P9999 停顿 │ 2000ms │ 20ms │ 99% ↓ │ │ 吞吐量损失 │ 3-5% │ 5-8% │ 2-3% │ │ 内存开销 │ 低 │ 极低 │ 更低 │ │ JDK 版本要求 │ JDK 8 │ JDK 11 │ - │ │ 配置复杂度 │ 高 │ 低 │ 更简单 │ │ │ └──────────────────────────────────────────────────────────────────┘四、效果验证4.1 ZGC 迁移后的 GC 日志# ZGC 日志 [2026-03-20T15:00:00.1230800: GC(12345) Garbage Collection Pause Mark Start 0.123456 ms 123.4 MB Pause Mark End 0.234567 ms 123.4 MB Pause Mark Start 0.345678 ms Concurrent Reset 1.234 ms Concurrent Mark 45.678 ms 0.0 MB Concurrent Mark 45.678 ms 789.1 MB Concurrent Cleanup 12.345 ms 1234.5 MB Pause CSet Start 0.123 ms Concurrent CSet 567.890 ms Pause CSet End 0.456 ms Concurrent 45.678 ms # 所有阶段停顿时间 1ms4.2 性能对比迁移前后对比 ┌──────────────────────────────────────────────────────────────────┐ │ 指标 │ 迁移前(G1) │ 迁移后(ZGC) │ 改善 │ ├────────────────────┼─────────────┼─────────────┼────────────┤ │ P50 停顿 │ 50ms │ 0.5ms │ 99% ↓ │ │ P99 停顿 │ 800ms │ 2ms │ 99.7% ↓ │ │ P999 停顿 │ 2000ms │ 5ms │ 99.75% ↓ │ │ P9999 停顿 │ 5000ms │ 20ms │ 99.6% ↓ │ │ 吞吐量 │ 92% │ 90% │ -2% │ │ 玩家卡顿率 │ 5% │ 0.1% │ 98% ↓ │ └──────────────────────────────────────────────────────────────────┘4.3 监控面板GC 停顿时间监控迁移后 ↑ 20ms ─┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────修复点 │ └──────┐ ↓ │ └──────┐ │ └────→ GC 停顿稳定在 10ms └──────────────────────────────────────────────────────→ 时间五、经验总结5.1 GC 选型决策树┌──────────────────────────────────────────────────────────────────┐ │ GC 算法选择决策树 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ Q: 吞吐量优先还是延迟优先 │ │ ├─ 吞吐量优先 → Parallel GC │ │ └─ 延迟优先 → 下一步 │ │ │ │ Q: JDK 版本是 │ │ ├─ JDK 8 → Shenandoah │ │ └─ JDK 11 → 下一步 │ │ │ │ Q: 堆大小是 │ │ ├─ 8GB → G1调优 │ │ ├─ 8-64GB → G1 或 ZGC │ │ └─ 64GB → ZGC │ │ │ │ Q: 延迟目标 │ │ ├─ P99 200ms → G1 调优 │ │ └─ P99 10ms → ZGC │ │ │ └──────────────────────────────────────────────────────────────────┘5.2 G1 调优参数速查关键参数 ┌──────────────────────────────────────────────────────────────────┐ │ 参数 │ 默认值 │ 调优建议 │ ├────────────────────────────────┼─────────┼──────────────────────┤ │ MaxGCPauseMillis │ 200ms │ 根据业务目标设置 │ │ G1HeapRegionSize │ 自动 │ 手动设置 4MB-32MB │ │ InitiatingHeapOccupancyPercent│ 45% │ 降低触发更频繁但可控 │ │ G1OldCSetRegionThresholdPercent│ 10% │ 降低减少混合 GC 时间 │ │ G1MixedGCLiveThresholdPercent │ 85% │ 提高只回收高存活分区 │ │ G1HeapWastePercent │ 5% │ 提高允许更多内存浪费 │ │ ConcGCThreads │ 自动 │ CPU核数/4 │ └─────────────────────────────────────────────────────────────────┘六、案例系列总结本系列五篇案例覆盖了 JVM 调优最常见的场景┌──────────────────────────────────────────────────────────────────┐ │ JVM 调优案例系列总结 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 案例一Full GC 频繁 │ │ → Survivor 太小 → 调 SurvivorRatio 晋升阈值 │ │ │ │ 案例二内存泄漏 │ │ → 静态缓存无 TTL → 使用 Caffeine 缓存框架 │ │ │ │ 案例三CPU 100% │ │ → 正则灾难性回溯 → 预编译 输入验证 │ │ │ │ 案例四线程死锁 │ │ → 循环依赖锁 → 固定加锁顺序 / TryLock │ │ │ │ 案例五GC 停顿过长 │ │ → Mixed GC 过于激进 → G1 调优或迁移 ZGC │ │ │ └──────────────────────────────────────────────────────────────────┘系列导航上一篇【JVM深度解析】第17篇JVM配置优化案例四线程死锁与接口超时诊断下一篇【JVM深度解析】第19篇JIT编译器深度解析系列目录JVM深度解析系列全集参考资料G1 GC Tuning GuideZGC DocumentationJEP 333: ZGC - A Scalable Low-Latency Garbage CollectorNetflix: Eliminating Large GC PausesG1 vs ZGC: Which is Right for You?