Skip to content
JVM 与进程排障 · 第 1 篇 / 共 3 篇
领域编程与框架
专题Java 专题
当前序列JVM 与进程排障
阅读位置第 1 篇 / 共 3 篇当前专题第 5 个序列 / 共 10 个序列

JVM 内存问题怎么分析:Heap Dump、MAT、Shallow Heap 和 Dominator Tree 入门

很多人第一次打开 MAT,会被一堆概念直接劝退:

  • Shallow Heap
  • Retained Heap
  • Dominator Tree
  • Histogram
  • Leak Suspects

其实这套工具不需要一上来全学会。先把最核心的几个视角理解清楚,已经足够处理大部分内存排查。

先说结论

分析 JVM 内存问题时,最重要的不是“会不会点 MAT 菜单”,而是搞清楚三件事:

  • 哪一类对象数量异常多
  • 哪些对象真正占住了大块内存
  • 为什么这些对象一直不能被回收

MAT 的价值,就是帮你把这三件事看清楚。

一、什么时候需要 Heap Dump

通常在这些场景下会考虑抓堆快照:

  • 内存持续上涨
  • Full GC 频繁
  • 服务没有明显流量,但堆一直回不去
  • 怀疑存在对象泄漏、缓存堆积、集合失控增长

常见命令:

bash
jmap -dump:format=b,file=heap.hprof <pid>

如果线上服务负载较高,抓取前要评估影响,不要把排障动作本身变成新的故障来源。

二、先理解四个关键概念

1. Shallow Heap

对象自身占用的内存大小。

比如一个 HashMap 对象本体可能不算大,但它引用的一大串节点、数组、值对象,未必算在它的 Shallow Heap 里。

所以:

  • Shallow Heap 适合看“对象本体大小”
  • 不适合单独判断“它到底拖住了多少内存”

2. Retained Heap

如果某个对象被回收,连带能一起释放掉的那部分内存大小。

它更接近我们真正关心的问题:

  • 这个对象如果没了,系统能腾出多少内存

因此在排查大对象时,Retained Heap 通常比 Shallow Heap 更有参考意义。

3. Dominator Tree

可以把它理解成“内存控制关系树”。

如果对象 B 到 GC Root 的所有路径都必须经过对象 A,那么就可以认为 A 支配 B。

这意味着:

  • A 很可能是那一片对象图的关键入口
  • 只要 A 还活着,下面那串对象通常就都活着

所以 Dominator Tree 很适合拿来找:

  • 谁真正占住了一大片内存
  • 哪个集合、缓存、上下文对象在拖着大量数据

4. Histogram

它更像对象分布统计表。

适合先回答这类问题:

  • 哪种类的实例最多
  • 哪种类总体占用最大

在排查开头,通常先看 Histogram 再深入到 Dominator Tree,效率会更高。

三、MAT 里最常用的几个入口怎么看

Histogram

先看对象数量和总体占用。

适合快速识别:

  • 某种 DTO、Map、byte[] 是否异常多
  • 是否出现大量重复对象

Dominator Tree

适合定位“谁在真正控制内存”。

如果发现一个大 Map、一个缓存对象、一个会话上下文的 Retained Heap 特别大,通常就值得继续往下钻。

Leak Suspects

它可以作为辅助入口,但不要把它当成最终结论。

因为它更像是:

  • 帮你快速列出可疑对象和引用链

真正下判断时,还是要自己回到引用关系和业务代码里验证。

四、一个实用排查流程

可以按这个顺序来:

  1. 先看 Histogram
  2. 找出数量异常或体积异常的类
  3. 再到 Dominator Tree 看是谁把它们留住
  4. 顺着 Path to GC Roots 看引用链
  5. 回到代码里判断这是正常缓存、正常上下文,还是确实没有释放

这个流程比“打开 MAT 到处乱点”有效得多。

五、哪些对象最值得优先怀疑

真实项目里,下面几类对象经常是高频嫌疑人:

  • 没有限流或淘汰策略的本地缓存
  • 过大的 HashMapConcurrentHashMap
  • 大量堆积的 byte[]
  • 长生命周期集合里挂住的业务对象
  • ThreadLocal 未清理导致的线程级残留

尤其当你在 Dominator Tree 里看到:

  • 某个集合对象 Retained Heap 很大
  • 引用链又明显通向缓存、会话、线程上下文

那基本就已经接近答案了。

六、分析 MAT 时容易踩的坑

1. 只看对象个数,不看引用关系

对象多不一定是问题,对象活得不该活才是问题。

2. 只看 Shallow Heap

很多“看起来很小”的对象,背后拖着一大片引用。

3. 把缓存误判成泄漏

不是所有大对象都是内存泄漏。

如果这是一个明确设计过的本地缓存,就要继续确认:

  • 是否有上限
  • 是否有过期机制
  • 当前大小是否符合业务峰值

4. MAT 自己内存配太小

Dump 文件越大,MAT 分析越吃内存。

如果 MAT 自己先 OOM,排查就会变得非常痛苦。

七、和 Java 进程排查怎么配合

如果问题更像:

  • CPU 飙高
  • 线程阻塞
  • 死锁

那就不能只看 Heap Dump,还要结合:

  • jstack
  • top -Hp
  • GC 日志

可以简单记成:

  • 堆快照偏向看“对象为什么留住了”
  • 线程栈偏向看“线程为什么跑不动了”

一句话总结

MAT 最核心的价值,不是告诉你“哪里可疑”,而是帮你回答:

  • 内存都被谁占了
  • 谁把这些对象留住了
  • 这些引用关系到底合不合理

HistogramRetained HeapDominator Tree 三件事看明白,JVM 内存分析就已经入门了。

延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 顺着当前主线继续读GC 日志怎么看适合把 GC、内存分析和 Java 进程排查放在同一条诊断主线上看。Java 专题 · JVM 与进程排障同一序列 · 顺着当前主线继续读Java 进程排查与 Heap Dump 使用记录适合把 GC、内存分析和 Java 进程排查放在同一条诊断主线上看。Java 专题 · JVM 与进程排障同专题其他序列 · Java 类加载与运行时细节对象是在堆上分配还是在栈上逃逸适合把类加载、双亲委派和运行时对象分配放到一起系统看。Java 专题 · Java 类加载与运行时细节同专题其他序列 · Java 类加载与运行时细节双亲委派模型到底解决了什么适合把类加载、双亲委派和运行时对象分配放到一起系统看。Java 专题 · Java 类加载与运行时细节跨专题关联 · 同场景:基础学习缓存穿透治理怎么做更稳适合把 Sentinel、穿透防护、延迟队列和地理位置搜索放在一起看。Redis 专题 · Redis 可用性与热点治理细节跨专题关联 · 同场景:基础学习慢 SQL 治理为什么不能只靠索引适合把大事务、DDL、回填、慢 SQL 治理和归档分层放进一条持续治理主线上看。MySQL 专题 · MySQL 变更治理与数据生命周期
继续阅读JVM 与进程排障当前序列第 1 篇 / 共 3 篇当前专题第 5 个序列 / 共 10 个序列
往前看
上一序列Java 线程池与异步编排从第 1 篇开始:线程池参数怎么配
往后看
下一篇GC 日志怎么看继续当前序列下一章下一序列Java 锁实现与并发细节从第 1 篇开始:synchronized 为什么仍然是 Java 并发里的默认首选

把零散经验整理成可查、可复用、可持续更新的企业级知识门户