leak suspects报告常无效,因其仅匹配mat内置的十几种泄漏模式,而真实业务泄漏多源于自定义缓存、spring bean隐式强引用或protobuf/json反序列化残留;报告为空不等于无泄漏,需转向dominator tree(按retained heap排序查top 3)、histogram(用正则过滤业务包+merge paths to gc roots)及跨dump对比(须同jdk版本、加-live、避开启动初期)。

Leak Suspects报告为什么经常没用
它只匹配 MAT 内置的十几种泄漏模式(比如静态 HashMap、未清理的 ThreadLocal),而真实业务里更多是自定义缓存逻辑、Spring Bean 间隐式强引用、或 Protobuf/JSON 反序列化后意外驻留的 ByteString。报告为空不等于没泄漏,只是没踩中预设规则。遇到空报告,直接切到 Dominator Tree 或 Histogram 手动筛。
Dominator Tree 怎么快速定位“真凶”
关键不是看谁 Shallow Heap 大,而是看谁 Retained Heap 最高——这个值代表:一旦删掉它,能释放多少内存。排序后前 3 行往往就是根因:
- 右键排第一的对象 →
Path to GC Roots→ 勾选exclude weak/soft/phantom references,只保留强引用链 - 重点盯住
static字段、java.lang.Thread实例、JNI Global这三类 GC Root,它们不会随对象生命周期自动释放 - 如果看到某
Service类的静态ConcurrentHashMap占了 1.2GB,且路径里有org.springframework.context.support.DefaultListableBeanFactory,基本可断定是 Spring 管理的单例 bean 持有了不该持有的引用
Histogram 里怎么过滤出业务类异常
别在全量类列表里手动翻,用正则精准收缩范围:
- 输入
com.yourcompany.*(注意末尾的.*)快速聚焦自己代码包下的类 - 按
Retained Heap或Objects排序,找那些实例数突增、或单个类 Retained Heap 超过 100MB 的条目 - 右键该类 →
Merge Shortest Paths to GC Roots,看这批对象共同被哪些 GC Root 持有;如果路径终点是某个ClassLoader,要警惕类加载器泄漏(尤其热部署场景)
对比两份堆转储时最易忽略的细节
单张 dump 是快照,两张才是证据链。但对比失效往往因为:
- 两次 dump 用的 JDK 版本不同(比如一次是 OpenJDK 17.0.2,一次是 17.0.8),对象字段布局微调会导致
Retained Size计算偏差 - 没加
-live参数用jmap抓取,混入大量已死对象,让Objects added列失真 - 基线 dump 抓得太早(刚启动 1 分钟),此时 Spring 初始化还没完成,很多缓存尚未填充,对比意义不大
真正可靠的对比,是用 jcmd <pid> VM.native_memory summary</pid> 配合堆转储,确认 native 内存没同步暴涨——否则可能根本不是 Java 堆泄漏,而是 JNI 或 DirectByteBuffer 溢出。











