直接看gc日志和堆快照可定位卡顿根源:先查full gc频率、耗时及老年代回收效果,再用jmap+mat分析支配树、实例数与引用链,结合jstack、top和监控图交叉验证,最后按泄漏、大对象、晋升过快或元空间不足分类处理。

直接看 GC 日志和堆快照,就能定位卡顿根源。频繁 Full GC 不是参数调得不对,而是内存使用出了问题。
查 GC 日志,确认是不是真频繁
先别猜,打开日志看真实数据。重点关注三项:
- Full GC 频率:正常是几小时一次,如果每分钟都来几次,就是异常
- 每次耗时:超过 1 秒就会影响响应,3 秒以上用户明显感知卡顿
- GC 后老年代占用率:比如“10G->9.8G”,回收后几乎没下降,说明对象根本没被释放
日志里像这样的行就是关键证据:[Full GC (Allocation Failure) 12.1G->11.9G(16G), 2.789 secs] —— 它告诉你不是内存不够,而是对象“死而不僵”。
抓堆快照,找谁占着老年代不走
用 jmap -dump:format=b,file=heap.hprof <pid></pid> 生成快照,再用 MAT 或 JProfiler 打开,重点看:
- 支配树(Dominator Tree):排在最上面的几个大对象,十有八九就是泄漏源头
-
按类统计实例数:比如
HashMap实例几十万、String十几万,结合代码查缓存或日志收集逻辑 - Reference Chain:对可疑对象右键“Path to GC Roots”,看是谁强引用着它不让回收
常见泄漏点:静态集合缓存没清理、监听器注册后没注销、ThreadLocal 没 remove、连接池资源未 close。
结合线程和系统指标交叉验证
单看内存还不够,要联动其他线索:
- 用
jstack <pid></pid>看是否有大量线程卡在java.lang.ref.ReferenceQueue.poll或java.util.HashMap.get—— 可能正被泄漏对象拖慢 - 用
top -H -p <pid></pid>查 GC 线程 CPU 占用是否持续飙高(比如 %CPU > 80%),说明 GC 在反复挣扎 - 对比监控图:Full GC 时间点是否和接口 RT 飙升、TP99 抖动完全重合?确认卡顿确实由 GC 引起,而非其他瓶颈
区分原因类型,针对性处理
根据现象快速归类,避免盲目调参:
- 内存泄漏:GC 后老年代不降 + 快照里有大量长期存活对象 → 修复代码,清理无用引用
-
大对象直入老年代:日志中 Full GC 前常伴随 “Promotion Failed” 或 “Allocation Failure” → 调小
-XX:PretenureSizeThreshold或优化对象大小 -
晋升过快:Young GC 频繁 + Survivor 区存活对象多 → 加大年轻代或调高
-XX:MaxTenuringThreshold -
元空间不足:日志出现 “Metaspace” 相关 Full GC → 增加
-XX:MetaspaceSize和-XX:MaxMetaspaceSize











