摘要
排查思路是“先在线定性,再离线定量”。
先用 jstat 定性,再用 jmap -histo 定位嫌疑对象,最后用 jcmd 精准 Dump。
永远不在生产环境执行 jmap -dump:live 或 jmap -histo:live,因为它们会强制触发 Full GC,在高并发场景下等同于自杀。
尤其是在云原生 + Pod 环境下,堆转储(Dump)本身耗时长、文件巨大、难以拉取,所以它绝不是首选工具。
第一步:快速定性——先用 jstat 看整体态势
先用 jstat -gcutil <PID> 1000 10
看两个核心指标:
· FGC 频率:如果 FGC 频繁且耗时飙升,说明老年代已满,可能存在内存泄漏或大对象直接晋升。
· YGC 频率:如果 YGC 每分钟几十次,说明年轻代过小或对象分配过快
通过这个快速定性,就能判断是“突发流量导致”还是“慢泄漏”,从而决定下一步动作。
第二步:在线取证——用 jmap -histo 锁定嫌疑对象
接下来,我会执行 jmap -histo <PID> | head -20(不带 :live,避免触发 GC)。这是一个低风险、秒级返回的操作,能直接告诉我们堆里数量最多、占用最大的是哪类对象。
这一步的价值在于:不需要 Dump 就能锁定 80% 的问题。
第三步:精准深挖——用 jcmd 抓 Dump 做离线分析(决策点)
什么情况下才去抓 Dump?
只有在直方图已经锁定某个类,但看不清引用链,才去抓快照。
此时我选用 jcmd <PID> GC.heap_dump,而非 jmap。原因是:jcmd对在线业务的影响更小,且在堆内存 >8G 时,STW 时间显著低于 jmap。
如果线上堆占用已经飙到 90% 以上,建议直接抓完快照立即重启应用“止血”,再离线用 MAT 分析。
第四步:临时止血(如果来不及分析)
如果 Full GC 已经让系统濒临崩溃,修复代码来不及,我会做两件事先止血:
· 增大年轻代(如堆16G时,将 -Xmn 从4G调到8G),减少 YGC 频率,给对象分配腾出更多空间。
· 切换 GC 器,从 CMS 切到 G1,并设置 -XX:MaxGCPauseMillis=50。G1 能自动处理大对象(Humongous Region),避免大对象直接晋升老年代。
第五步:根因治理(代码层面)
当 Dump 分析锁定根因后,我会从代码层面进行治理。比如,如果是某个 SQL 查询导致的大对象,我会改为分批次查询,避免 ResultSet 一次性拉取全部数据到内存。