G1退化是触发Serial Old Full GC的严重信号,表现为单线程、长STW、老年代回收失控;需通过日志线程数、停顿时间、排除伪Full GC三方面确认,并优化参数与排查晋升异常、大对象、内存泄漏等问题。

G1 在并发标记或 Mixed GC 过程中,若发现老年代空间严重不足、晋升压力过大或无法及时回收足够 Region,会触发退化(degeneration),转而使用单线程的 Serial Old 收集器执行 Full GC。此时 STW 时间常达数秒,直接导致接口超时、服务抖动。这不是配置“没调好”的小问题,而是 G1 已经丧失可控回收能力的明确信号。
识别退化是否真实发生
不能仅凭日志里出现 “Full GC” 就断定是 Serial Old 退化。需重点确认三点:
- GC 日志中明确含 Full GC (Ergonomics) 或 Full GC (Allocation Failure),且线程数为 1(如
[PSYoungGen: ...] [ParOldGen: ...]消失,只留[Tenured: ...]) - 停顿时间(real time)显著高于正常 Mixed GC(如 2800ms vs 50ms),且与业务超时阈值吻合
- 排除 Metaspace OOM、Direct Memory 耗尽等“伪 Full GC”——它们虽打印 Full GC,但根源不在老年代分配失败
关键参数:让 Mixed GC 稳住不退化
目标是让并发标记早启动、Mixed GC 多干活、老年代压力不堆积。以下参数组合已在多个 32GB+ 堆生产环境验证有效:
- -XX:InitiatingHeapOccupancyPercent=35~40:默认 45,若老年代增长快(如每分钟涨 1GB),降至 35 可提前触发并发标记,避免标记完成前就 OOM
- -XX:G1MixedGCLiveThresholdPercent=85:只回收存活率 ≤85% 的老年代 Region,跳过“太干净”的 Region,把有限 GC 资源集中在真正有回收价值的区域
- -XX:G1ReservePercent=15~20:预留 15%~20% 堆空间专供 Mixed GC 晋升使用,防止 GC 中因无空闲 Region 被迫退化
- -XX:G1HeapRegionSize=16m 或 32m:64GB 堆建议设为 32MB;Region 过小(如默认 2MB)易导致大对象直入 Humongous 区,加剧碎片和退化风险
必须排查的非参数原因
参数再合理,若存在以下问题,退化仍会高频发生:
立即学习“Java免费学习笔记(深入)”;
-
对象晋升异常:用
-XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy观察 Young GC 后晋升量。若平均每次晋升 > 老年代剩余空间,说明 Survivor 区太小或-XX:MaxTenuringThreshold过低 -
大对象直入老年代:日志中频繁出现
Humongous allocation。典型场景包括:未分页加载百万级 List、一次性反序列化 50MB JSON、缓存未压缩的图片字节数组 -
内存泄漏或隐式晋升:用
jstat -gc <pid> 5000持续采样,观察 OU(Old Used)是否阶梯式上涨且 Full GC 后不回落;或用 MAT 分析 heap dump 中长期存活的大集合引用链 - 显式 System.gc():检查代码及第三方 SDK(如旧版 Druid 连接池、某些 Netty 工具类),禁用或替换为 Cleaner/PhantomReference
验证优化是否生效
不要只看“有没有 Full GC”,要盯三个核心指标:
- Mixed GC 频次占比:理想情况应 ≥70%,即大部分老年代回收由 Mixed 完成,而非退化
- Concurrent Marking Duration:并发标记总耗时应稳定在 200~800ms,若持续 >1500ms,说明标记线程不足或堆太脏
- Old Gen Occupancy after Mixed GC:每次 Mixed GC 后老年代占用率应明显下降(如从 75% → 50%),而非反复卡在 80%+


















