这是「一文拿下 JVM」系列的番外篇。所有 JVM 问题中,OOM 是最让开发者头疼的——它发生的那一刻往往不可逆,需要 dump 文件才能复盘。本篇讲清「怎么留证据 + 怎么读证据」。
一、让 JVM 自动导出堆镜像
经验之谈:线上服务第一天上生产环境,就应该把这两个参数加上。
-XX:+HeapDumpOnOutOfMemoryError # 发生 OOM 后导出堆内存镜像
-XX:HeapDumpPath=/var/log/oom/ # 堆内存镜像的文件保存位置
加了这两个参数后,JVM 在 OOM 发生时会自动写一个 java_pid<pid>.hprof 文件到指定目录。
为什么不手动 jmap?
- OOM 发生的瞬间,进程往往立即退出——你来不及手动 jmap
- jmap 导出 dump 时会触发一次 Full GC,可能让现场被破坏
jmap -dump:format=b,file=./xxx.hprof <pid>在大堆(8G+)上导出会让服务卡顿几十秒,影响线上

二、用 jmap 手动导出
如果忘了加自动参数、或者你需要在线 dump 当前状态:
jmap -dump:format=b,file=./xxx.hprof <pid>
dump 文件大小约等于堆使用量。4G 堆大约产生 4G 左右的 dump——磁盘要预留够。
三、MAT(Memory Analyzer)实战
Eclipse 出品的免费堆分析工具,2017 年依然是 dump 分析的首选。
下载地址:https://www.eclipse.org/mat/
3.1 打开 dump 文件
启动 MAT → File → Open Heap Dump → 选择 xxx.hprof。
3.2 看对象数量
点击下方第二个图标(Leak Suspects 或 Histogram),按类名排序,看实例数最多的类。

关键看三点:
- 实例数量前几的类(看有没有异常的量级)
- 每个实例的 Retained Heap(看是不是大对象霸占内存)
- 类的引用链(看是谁在持有这些对象)
3.3 看字节数
点击第三个图标(Dominator Tree),按 Retained Heap 倒序排列。
Retained Heap = 这个对象被回收后能释放的内存总量。一个大对象 + 它引用的所有对象 = 它的 Retained Heap。这是判断内存泄漏的核心指标。
3.4 看 GC Roots 引用链

查看某个类被哪个 GC Roots 引用了——下面的选择可以排除软、弱、虚引用,只保留强引用。
实战技巧:内存泄漏时,往往能看到一个本应该被回收的对象,一直被某个静态集合持有。沿着 GC Roots 一路追下来,看到底是谁在 hold 它——这就是泄漏的源头。
四、JVisualVM 实战
JDK 自带工具,当年很多人忽略它,其实功能非常强。
启动方式:终端输入 jvisualvm(JDK 8 及以下自带,9+ 需单独下载)。
4.1 监控本地 / 远程 Java 进程
可监控本地和远程 Java 进程的状态:
- 有类似 MAT 的内存映像分析功能
- 可直接分析,也能打开 jmap 导出的文件分析
- 能展示 Java 进程中进程相关的信息(类似于
jstack <pid>的输出结果图像化展示)
4.2 抽样器(Profiler)
抽样器可以对 CPU 和内存进行抽样。
CPU 抽样:

开启对 CPU 的抽样,可以看到每个方法执行的时间。通过此手段获取热点方法,分析方法执行耗时。
实战技巧:CPU 飙高时,Sampler 比 Profiler 更安全。Sampler 定期采样,Profiler 插桩会让方法执行速度变慢,线上慎用。
内存抽样:
开启对内存的抽样,可以得到类占用的内存统计,可用以分析内存泄漏。
4.3 插件安装
在 JVisualVM 中,通过:工具 → 插件 可以安装 JVisualVM 的插件。
重要:安装的插件需要匹配当前 JDK 的版本。在上述弹框的设置页签,配置插件中心地址。获取插件中心地址方法:去 GitHub 上,查询 JVisualVM 项目说明的插件地址。
五、远程监控配置
在启动的 Java 进程实例中加入下述参数(用 JMX 远程连接):
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Djava.rmi.server.hostname=<服务器IP>
然后在 JVisualVM 里 File → Add JMX Connection 填 <IP>:9010 即可。
当年踩过的坑:
java.rmi.server.hostname必须填公网 IP 或内网 IP——填localhost会让本地连接不上。这是 2017 年很多新手的常见误区。
六、OOM 排查标准流程
当线上 OOM 报警时:
- 保留现场:立刻
jstack <pid> > thread.txt抓线程栈 - 抓 dump:用
jmap -dump:format=b,file=dump.hprof <pid>(如果没加自动参数) - 保留 GC 日志:复制
gc.log出来 - 重启服务:用
kill -9强杀后重启——别让 OOM 拖垮整个集群 - 事后分析:用 MAT 打开 dump → Histogram 看大对象 → Dominator Tree 看 Retained Heap → GC Roots 追引用链
- 修复上线:找到泄漏点(通常是静态集合 / 监听器 / 线程局部变量忘了清理)→ 修改代码 → 灰度发布
铁律:永远不要在 OOM 后不做 dump 就重启服务。否则 OOM 会反复发生,而你永远不知道根因。
七、MAT vs JVisualVM 对比
| 维度 | MAT | JVisualVM |
|---|---|---|
| 离线分析 dump | 非常强(OQL 查询、Retained Heap 算法) | 一般(基础视图) |
| 在线监控 | 无 | 非常强(CPU/内存/线程/GC 实时) |
| 远程监控 | 无 | 支持(JMX) |
| OQL 查询 | 支持 | 不支持 |
| 安装 | 独立下载 | JDK 自带 |
| 内存占用 dump 分析 | 较低 | 较高(dump 大时容易 OOM) |
实战建议:MAT 用于事后分析 dump,JVisualVM 用于事中监控。两者配合,绝配。
八、小结
OOM 排查的核心心法:
- 预防:上线前就加好
-XX:+HeapDumpOnOutOfMemoryError - 抓现场:jstack + jmap + GC 日志三件套
- 分析:MAT 看对象 + Dominator + GC Roots
- 定位:找到强引用链的源头
- 修复:业务代码 + 配置参数双管齐下
下一篇「一文拿下 JVM(终章):对象创建的 7 个步骤」回到基础——一个
new Object()在 JVM 内部到底经历了什么。