jmap 堆转储与对象直方图
更新时间:2026-09-18。配套工具文:jmap 堆 Dump 与对象统计实战。本篇聚焦从直方图到 MAT 引用链的定位路径。
一、两种武器:histo 与 dump
bash
# 1) 在线直方图:不导文件,快速看哪类对象最多(影响小,可先做)
jmap -histo:live <pid> | head -30
# num #instances #bytes class name
# 带 :live 会先 Full GC 再统计,只看存活对象;不加 live 看全部(含垃圾)
# 2) 完整堆转储,交给 MAT/JProfiler 分析
jmap -dump:live,format=b,file=/tmp/heap.hprof <pid>
jcmd <pid> GC.heap_dump /tmp/heap.hprof # JDK8+ 推荐| 方式 | 开销 | 用途 |
|---|---|---|
-histo | 小、秒级 | 先粗看对象数量/字节 Top 类 |
-dump | 大、STW、文件≈堆大小 | 精确定位引用链 |
二、MAT 怎么看
用 Eclipse MAT 打开 hprof:
- Dominator Tree(支配树):按 Retained Size(该对象被回收后能连带释放的内存)排序,一眼找最大的「内存霸主」。
- Path to GC Roots:对可疑对象看「谁引用着它」,一路追到静态集合、缓存、ThreadLocal。
- Leak Suspects 报告:MAT 自动给出疑似泄漏点。
两个大小概念:
- Shallow Size:对象自身占用。
- Retained Size:对象 + 它独占引用的所有对象的总内存——定位泄漏看这个。
三、典型泄漏长什么样
| 现象 | 引用链根因 |
|---|---|
| 某 HashMap 占比持续涨 | static Map 只 put 不 remove |
| 业务对象实例数随时间增长 | 自建缓存无淘汰/过期 |
大量 ThreadLocalMap$Entry | ThreadLocal 用完没 remove() |
| Class 相关对象暴涨 | 动态代理/热部署类加载器泄漏(Metaspace) |
排查宏观流程见 Full GC 频繁怎么排查,内存模型见 JVM 运行时内存与堆分代。
四、常见坑
- 高峰期直接
-dump:live:触发 Full GC + STW,且 dump 大文件会让服务卡顿/磁盘打满;应在低峰或已摘流量的节点做。 - 没加
live误判:直方图里大量本会被回收的垃圾对象干扰判断;要看泄漏用:live(但它会先 Full GC)。 - dump 文件没地方放:hprof 大小接近堆,先确认磁盘空间大于
-Xmx。 - 只看 Shallow 不看 Retained:集合自身不大,但它持有的百万对象才是问题,必须用支配树看 Retained。
- 重启才想起来没留现场:生产加
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=,OOM 自动留堆。
总结
jmap 先用 -histo:live 在线看存活对象直方图、按字节数粗定位异常多的类,再用 jmap -dump:live 或 jcmd GC.heap_dump 导出约堆大小的 hprof,用 MAT 的支配树按 Retained Size 找内存霸主、用 Path to GC Roots 追到 static Map、无淘汰缓存、未 remove 的 ThreadLocal 或泄漏类加载器;注意 live 会先触发 Full GC、dump 有 STW 且文件巨大,必须低峰或摘流并预留磁盘,分析时看 Retained 而非 Shallow Size,同时用 HeapDumpOnOutOfMemoryError 提前留好 OOM 现场。