排查g1不可控停顿需聚焦偏离预期节奏的信号:单次stw超200ms、mixed gc频次异常或full gc误触发;须结合日志(如evacuation failure)、内存指标(eden未满即回收)及诱因(大对象、并发标记失败)交叉验证。

排查 G1 收集器中不可控的停顿问题,关键不是盯着“有没有停顿”,而是看停顿是否偏离预期节奏——比如单次 STW 超过 200ms、Mixed GC 频率异常升高、或出现本不该发生的 Full GC。G1 的停顿由多个阶段共同决定,需从日志、指标、内存行为三方面交叉验证。
看懂 G1 日志里的停顿信号
启用基础日志参数(-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log)后,重点关注以下字段:
- Pause (G1 Evacuation Pause) 类型:区分是 young、mixed 还是 full。若日志中频繁出现 mixed 且耗时 >300ms,说明老年代压力大或 Region 碎片化严重
- Ext Root Scanning 耗时突增:该阶段扫描线程栈、JNI 引用等根对象,若平均值从几毫秒跳到 40ms+,可能因线程数暴增或反射/代理类过多
- Object Copy 时间拉长:反映存活对象多、复制开销大,常见于 Survivor 区太小导致对象提前晋升,或大对象(Humongous)频繁分配
- Evacuation Failure 记录:日志中出现 “to-space overflow” 或 “evacuation failed”,意味着无法完成 Region 搬迁,会触发退化 Full GC
盯住内存变化的异常节奏
用 jstat -gc
- Eden 使用率未打满就回收:正常应接近 100% 才触发 YGC;若常在 60–80% 就回收,说明对象存活率高,Survivor 空间不足或晋升阈值过低
- 老年代使用量阶梯式上升:每次 Mixed GC 后老年代残留不降反升,可能是内存泄漏,也可能是大对象直接进入老年代未被及时回收
- Humongous 区持续增长:G1 中 ≥½ Region 大小的对象即为大对象,直接分配在老年代连续 Region 中;若 HumongousOccupiedCount 持续增加且不释放,需检查 byte[]、String、Map 等大结构生成逻辑
识别典型诱因并快速定位
多数不可控停顿背后有共性线索,可按优先级排查:
- 大对象冲击:定时任务、文件上传、序列化反序列化易产生大数组或大集合;通过 -XX:+PrintAdaptiveSizePolicy 和堆直方图(jmap -histo)确认高频大对象类型
- 并发标记跟不上分配速度:CMS 已废弃,但 G1 同样存在 concurrent mode failure 风险;若日志中出现 “Concurrent Cycle Abort”,说明初始标记或重标记阶段被业务线程拖慢,需调大 -XX:G1ConcRefinementThreads 或降低 -XX:G1MixedGCCountTarget
- Region 碎片化+晋升失败:尤其在堆较大(>8GB)、对象生命周期混杂的场景;可通过 -XX:+UnlockExperimentalVMOptions -XX:+PrintGCDetails -XX:+UseG1GC 加 -XX:+PrintG1RegionInfo 查看 Region 分布,确认 old 区是否大量零散小 Region
- 系统资源争抢:CPU 负载长期 >80%、TLAB 分配失败率高(日志中 “TLAB waste” 显著)、或开启 -XX:+UseLargePages 但 OS 未真正分配大页,都会放大 STW 时间
调参不是万能,但要避开明显陷阱
有些配置看似合理,实则加剧停顿:
- 别硬设新生代大小:如 -XX:G1NewSizePercent=30,会剥夺 G1 动态调节能力,反而导致 Mixed GC 触发过早或过晚
- MaxGCPauseMillis 不宜过激:设成 50ms 容易让 G1 收集范围过小,垃圾堆积后被迫 Full GC;建议从 200ms 起步,结合真实负载逐步下调
- Region 大小慎调:-XX:G1HeapRegionSize 默认由堆大小自动计算(≈ 堆/2048),手动设为 4MB 可减少大对象数量,但会增加标记和回收阶段的 STW 开销
- 避免显式 GC 干扰:禁用 -XX:+ExplicitGCInvokesConcurrent,防止 System.gc() 打断正常并发周期











