必须用 jmap -dump:live 而非默认 dump,因为当 eden 区频繁溢出但未触发 full gc 时,普通 dump 会包含大量本该回收的死亡对象,干扰根因分析;加 live 参数可强制先 gc 再 dump,确保仅保留真实存活对象。

什么时候必须用 jmap -dump:live 而不是默认 dump
当堆里存在大量短生命周期但未及时回收的小对象(比如循环中 new 的 String、HashMap$Node、ArrayList 等),JVM 可能还没触发 Full GC,但 Eden 区已频繁溢出,OutOfMemoryError: Java heap space 就会直接抛出。此时如果只执行 jmap -dump:format=b,file=heap.hprof PID,dump 出来的文件会包含大量“本该被回收却还挂着引用”的死亡对象,干扰 MAT 判断真正滞留的根因。
正确做法是强制只 dump 当前存活对象:
-
jmap -dump:live,format=b,file=heap_live.hprof PID—— 加live参数让 JVM 先做一次 GC 再 dump,确保文件只反映真实存活对象集合 - 注意:该操作会触发一次 Full GC,线上慎用;若服务无法承受停顿,优先考虑
-XX:+HeapDumpOnOutOfMemoryError自动捕获 - 避免在 GC 压力已很高的节点上反复执行,可能加剧卡顿甚至雪崩
jmap -histo 快速定位海量小对象的类名和数量
在 dump 前,先用 jmap -histo PID | head -n 50 查看堆内对象分布。重点关注三列:instances(实例数)、bytes(总字节)、class name(类名)。出现以下信号要立刻怀疑:
- 某个类的
instances达到百万甚至千万级,但单个bytes很小(如java.lang.String平均 40–100 字节) - 大量匿名内部类或 Lambda 生成的类(如
com.example.Service$$Lambda$123/0x0000000800a1b000),说明可能有闭包持有外部大对象 - 常见嫌疑类:
java.util.HashMap$Node、java.util.ArrayList、java.lang.Object[]、char[]、byte[]
示例输出中若看到:
Eclipse IDE 是一款由 Eclipse 基金会管理的开源、跨平台集成开发环境。其核心基于 Java 构建,通过强大的插件架构可扩展支持 C/C++、Python、PHP 等多种编程语言。它提供丰富的代码编辑、调试和重构工具,并紧密集成 Git、Maven 等现代开发工具链,是全球众多开发者首选的 Java 开发利器。
1: 2847652 136687296 java.util.HashMap$Node 2: 2847652 113906080 java.lang.Object[] 3: 2847652 68343648 java.lang.String
这基本就是同一个 HashMap 不断 put 导致的链表/红黑树节点膨胀,不是孤立小对象,而是结构型堆积。
MAT 中重点看 Dominator Tree 和 Retained Heap 排序
打开 heap_live.hprof 后,别急着点 Leak Suspects——它对海量小对象效果有限,容易淹没在噪声里。直接打开 Dominator Tree 视图,并按 Retained Heap 降序排列:
-
Retained Heap高的,才是真正“卡住”内存不放的对象;Shallow Heap高的只是自身大,未必是根因 - 找那些
Retained Heap占比 >10% 且类型为容器类(HashMap、ConcurrentHashMap、ArrayList、静态缓存 Map)的条目 - 右键 →
Path to GC Roots→ 勾选exclude weak/soft/phantom references,看谁在强引用持有它 - 特别注意
java.lang.Thread下挂载的LocalVariable或static字段,这是线程局部缓存或单例滥用的典型痕迹
用 OQL 查找特定小对象的分布模式
当怀疑某类小对象(如 byte[])被高频创建但未复用时,MAT 的 OQL(Object Query Language)比图形界面更高效:
- 打开
Query Browser→Open Console - 执行:
SELECT * FROM INSTANCEOF byte[] WHERE @length > 1024 AND @length ,查出中等长度的 byte 数组(常用于临时 IO 缓冲) - 再执行:
SELECT s.@classof, COUNT(*) FROM OBJECTS s WHERE s.@classof.toString().contains("String") GROUP BY s.@classof,统计 String 实例的 ClassLoader 分布,判断是否由多个热部署模块重复加载导致 - OQL 结果可导出 CSV,用 Excel 看频次分布,比人工翻页快得多
海量小对象的问题本质不是“单个对象大”,而是“引用关系网太密、GC root 太深”。MAT 里最容易被忽略的一点是:不要只盯着 Leak Suspects 报告里的前几条,而要结合 histo 输出 + Dominator Tree + OQL 交叉验证,否则很可能修了 A 类对象,B 类又顶上来。










