老年代晋升过快可提前识别:通过gcviewer中old gen usage阶梯上升、pause time骤增伴promotion飙升、gc cause中allocation failure占比超90%三类信号判断;再结合tenuring distribution中threshold卡在2/3且age 1占survivor超60%,定位 survivor 不足或分配过快;最后用jmap -histo交叉验证大数组、map节点或string驻留等肇事对象,并排除oom killer、cpu争抢及cms碎片等干扰。

不用等 Full GC 报警才动手——老年代晋升过快的苗头,GCViewer 里几秒就能抓出来。关键不是看“老年代用了多少”,而是盯住每次 Young GC 后,对象是怎么、有多少、多快涌进老年代的。
一眼识别晋升异常的三类图表信号
打开 GCViewer 加载日志后,直接看这三个视图:
- “Old Gen Usage”趋势图:如果出现密集、等距、阶梯式上升(比如每次 Minor GC 后固定 +18MB~25MB),且 Full GC 后几乎不回落,就是典型“只进不出”的过早晋升;
- “Pause Time”直方图:Young GC 停顿时间突然从 10ms 跳到 40ms+,同时“Promotion”列数值同步飙升(GCViewer 会解析并标出每次晋升量),说明 Survivor 区在硬扛,复制压力已转为晋升压力;
- “GC Cause”分布饼图:若 “Allocation Failure” 占比超 90%,且对应时段“Old Gen Used”曲线斜率陡增,基本排除大对象直入或 CMS 并发失败,锁定是年轻代对象“绕路”进老年代。
用 Tenuring Distribution 表格定位年龄断层
GCViewer 的“Details”页会还原 -XX:+PrintTenuringDistribution 输出。重点看两行:
- “New threshold”值是否长期卡在 2 或 3(远低于默认 15);
- “age 1”行的 total 字节数是否占当轮 Survivor 总容量的 60% 以上(例如:Survivor 容量 64MB,age 1 total 达 42MB)。
这两项同时出现,说明 JVM 已被迫降低年龄阈值——对象活不过一次 GC 就被推去老年代,根源通常是 Survivor 空间不足或分配速率过高。
交叉验证:结合 jmap -histo 快速指认“肇事对象”
在 GCViewer 中发现异常时段后,立刻对齐时间戳,执行:
jmap -histo:live重点关注:
- byte[] / char[] 实例数超 50 万,且总大小 >1GB:大概率是日志框架缓冲区、JSON 序列化临时数组或未关闭的 InputStream;
- ConcurrentHashMap$Node 或 LinkedHashMap$Entry 单个实例小(~40B)、但数量爆炸(百万级):说明 Map 在高频 put/remove,但 key 或 value 持有长生命周期引用,导致 Entry 无法回收;
- String 对象 Shallow Heap 小、Retained Heap 巨大:检查 substring 或 intern 使用,常因字符串驻留引发间接泄漏。
排除干扰,确认真由代码触发
别急着改代码,先扫清系统层假象:
- 查 dmesg -T | grep -i "killed process",排除 OOM Killer 杀进程伪造的 Full GC;
- 跑 vmstat 1 10,确认 r 列是否持续 > CPU 核数,swpd 是否为 0——避免把 CPU 抢占误判为 GC 卡顿;
- 检查是否启用 CMS 且未配 -XX:+UseCMSCompactAtFullCollection,这种组合容易因碎片触发假性晋升压力。











