跳至正文
来两杯美式
返回

一文拿下 JVM(番外):OOM 排查与 MAT / JVisualVM 实战

By 来两杯美式
发布于

这是「一文拿下 JVM」系列的番外篇。所有 JVM 问题中,OOM 是最让开发者头疼的——它发生的那一刻往往不可逆,需要 dump 文件才能复盘。本篇讲清「怎么留证据 + 怎么读证据」。

一、让 JVM 自动导出堆镜像

经验之谈:线上服务第一天上生产环境,就应该把这两个参数加上

-XX:+HeapDumpOnOutOfMemoryError     # 发生 OOM 后导出堆内存镜像
-XX:HeapDumpPath=/var/log/oom/      # 堆内存镜像的文件保存位置

加了这两个参数后,JVM 在 OOM 发生时会自动写一个 java_pid<pid>.hprof 文件到指定目录。

为什么不手动 jmap?

  1. OOM 发生的瞬间,进程往往立即退出——你来不及手动 jmap
  2. jmap 导出 dump 时会触发一次 Full GC,可能让现场被破坏
  3. jmap -dump:format=b,file=./xxx.hprof <pid> 在大堆(8G+)上导出会让服务卡顿几十秒,影响线上

JVM 自动导出 OOM dump

二、用 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 SuspectsHistogram),按类名排序,看实例数最多的类

MAT Histogram 视图

关键看三点:

  1. 实例数量前几的类(看有没有异常的量级)
  2. 每个实例的 Retained Heap(看是不是大对象霸占内存)
  3. 类的引用链(看是谁在持有这些对象)

3.3 看字节数

点击第三个图标(Dominator Tree),按 Retained Heap 倒序排列。

Retained Heap = 这个对象被回收后能释放的内存总量。一个大对象 + 它引用的所有对象 = 它的 Retained Heap。这是判断内存泄漏的核心指标

3.4 看 GC Roots 引用链

MAT GC Roots 引用

查看某个类被哪个 GC Roots 引用了——下面的选择可以排除软、弱、虚引用,只保留强引用。

实战技巧:内存泄漏时,往往能看到一个本应该被回收的对象,一直被某个静态集合持有。沿着 GC Roots 一路追下来,看到底是谁在 hold 它——这就是泄漏的源头。

四、JVisualVM 实战

JDK 自带工具,当年很多人忽略它,其实功能非常强。

启动方式:终端输入 jvisualvm(JDK 8 及以下自带,9+ 需单独下载)。

4.1 监控本地 / 远程 Java 进程

可监控本地和远程 Java 进程的状态:

4.2 抽样器(Profiler)

抽样器可以对 CPU 和内存进行抽样。

CPU 抽样

JVisualVM 插件

开启对 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 报警时:

  1. 保留现场:立刻 jstack <pid> > thread.txt 抓线程栈
  2. 抓 dump:用 jmap -dump:format=b,file=dump.hprof <pid>(如果没加自动参数)
  3. 保留 GC 日志:复制 gc.log 出来
  4. 重启服务:用 kill -9 强杀后重启——别让 OOM 拖垮整个集群
  5. 事后分析:用 MAT 打开 dump → Histogram 看大对象 → Dominator Tree 看 Retained Heap → GC Roots 追引用链
  6. 修复上线:找到泄漏点(通常是静态集合 / 监听器 / 线程局部变量忘了清理)→ 修改代码 → 灰度发布

铁律永远不要在 OOM 后不做 dump 就重启服务。否则 OOM 会反复发生,而你永远不知道根因。

七、MAT vs JVisualVM 对比

维度MATJVisualVM
离线分析 dump非常强(OQL 查询、Retained Heap 算法)一般(基础视图)
在线监控非常强(CPU/内存/线程/GC 实时)
远程监控支持(JMX)
OQL 查询支持不支持
安装独立下载JDK 自带
内存占用 dump 分析较低较高(dump 大时容易 OOM)

实战建议:MAT 用于事后分析 dump,JVisualVM 用于事中监控。两者配合,绝配。

八、小结

OOM 排查的核心心法:

下一篇「一文拿下 JVM(终章):对象创建的 7 个步骤」回到基础——一个 new Object() 在 JVM 内部到底经历了什么。


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
所属专题
JVM
第 4 / 6 篇
查看系列全部文章
  1. 01.一文拿下 JVM(上):内存区域全景
  2. 02.一文拿下 JVM(中):参数配置与常用命令
  3. 03.一文拿下 JVM(下):逃逸分析与代码优化
  4. 04.一文拿下 JVM(番外):OOM 排查与 MAT / JVisualVM 实战
  5. 05.一文拿下 JVM(终章):对象创建的 7 个步骤
  6. 06.一文拿下 JVM(锁篇):synchronized 锁的升级与三种锁对比

上一篇
一文拿下 JVM(终章):对象创建的 7 个步骤
下一篇
一文拿下 JVM(下):逃逸分析与代码优化