核心要区分堆内对象长期存活还是堆外内存异常增长:先用jstat -gcutil <pid> 2000看OU是否持续上升且FGC后仅微降,再用jmap -histo或NMT对比定位堆内/堆外增长源,最后抓堆转储用MAT分析GC Roots确认泄漏根因。

Java 中 JVM 内存飙高且不释放,核心要区分是“堆内对象长期存活”还是“堆外内存异常增长”,再按顺序验证、定位、确认。不能一上来就调大 -Xmx,也不能只盯着 top 的 RES 值看。
先看 GC 行为,确认是不是真泄漏
用 jstat 快速判断老年代回收是否失效:
- jstat -gcutil <pid> 2000(每2秒刷新一次)重点关注 OU(Old Used)和 FGC(Full GC 次数)
- 如果 OU 持续缓慢上升,每次 Full GC 后只下降一点点(比如从 95% → 92%),同时 FGC 频次增加,基本可判定存在内存泄漏
- EU(Eden 使用率)频繁波动属正常;若 EU 长期高位不动,可能是对象分配速率极低或 GC 被阻塞
分清堆内还是堆外,避免方向错误
堆内存只是 JVM 内存的一部分,RES 高 ≠ 堆高:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
堆内问题:用 jmap -histo:live <pid> | head -n 20 查看存活对象 TOP20,重点关注
byte[]、String、HashMap$Node、ArrayList实例数是否异常多 -
堆外问题:启用 NMT(Native Memory Tracking):
启动加参数-XX:NativeMemoryTracking=detail,然后执行jcmd <pid> VM.native_memory summary.diff(对比内存上涨前后的差异)
重点观察Metaspace、Direct Buffer、Thread、Internal区域是否明显增长 - 补充验证:用 pmap -x <pid> 看各内存段大小,若
[anon]或libjvm.so映射远超 -Xmx 设置,大概率是 DirectByteBuffer 或 JNI 未释放
抓堆转储,定位具体泄漏对象
确认是堆内问题后,主动 dump 比等 OOM 更可控:
立即学习“Java免费学习笔记(深入)”;
- jmap -dump:format=b,file=heap.hprof <pid>(确保磁盘空间充足)
- 用 Eclipse MAT 打开,优先看 Leak Suspects Report,它会自动标出最可疑的引用链
- 再切换到 Dominator Tree,按 retained heap 排序,点开最大对象,展开 Path to GC Roots,重点排查:
– static 引用(如静态 Map/Cache)
– ThreadLocal 变量未清理
– 监听器/回调注册后未注销
– 缓存无过期、无容量限制
结合 GC 日志验证回收逻辑
GC 日志是判断问题性质的关键证据:
- 启动加参数:
-Xlog:gc*:file=gc.log:time,uptime,pid,tags(JDK 9+) - 关注日志中每次 Full GC 前后 Old 区的 used 值变化,若回收后仍接近 -Xmx,说明对象无法被释放
- 留意是否有
promotion failed、concurrent mode failure等警告,提示新生代晋升失败或 G1/CMS 并发失败,可能与堆配置或分配速率不匹配有关

















