Eclipse Memory Analyzer(MAT)入门教程:从生成 Heap Dump 到定位 Java 内存问题

在排查 TongWeb、Tomcat、Spring Boot 等 Java 服务的内存问题时,经常会遇到这样的情况:

复制代码
JVM 内存越来越高
↓
Full GC 越来越频繁
↓
GC 后内存仍然降不下来
↓
最终 OutOfMemoryError

这时候,仅仅通过 topjstat 查看 JVM 内存,只能知道"内存有问题",却很难知道到底是什么对象占用了内存

这时候就可以使用 Eclipse Memory Analyzer(MAT)

简单来说:

  • MAT 就是一款专门用来分析 Java Heap Dump 的工具,可以帮助我们找到占内存最多的对象,以及这些对象为什么一直没有被 GC 回收。
  • 最主要的是完全免费,不需要进行pojie等操作。

MAT 主要用来解决什么问题?

MAT 最常见的使用场景主要有以下几种:

1. Java 服务频繁 OOM

例如日志出现:

复制代码
java.lang.OutOfMemoryError: Java heap space

可以在 JVM OOM 后生成的 Heap Dump 中寻找问题。

2. JVM 内存持续上涨

例如:

复制代码
启动:2G
运行一天:3G
运行三天:5G
运行一周:7G

而且 Full GC 之后内存仍然很高,就需要重点怀疑对象没有正常释放。

3. Full GC 频繁

如果发现 JVM 不断进行 Full GC:

复制代码
Full GC
Full GC
Full GC

但是回收效果越来越差,也可以通过 Heap Dump 分析到底是什么对象占用了堆内存。

4. 怀疑内存泄漏

例如:

复制代码
缓存没有清理
ThreadLocal 使用不当
静态 Map 持有大量对象
Session 保存大量数据
Web 应用重复部署后 ClassLoader 没有释放

这些问题都可以通过 MAT 进一步分析。


MAT 的整体排查思路

第一次使用 MAT,不需要把所有功能都学会。

记住下面这条路线就够了:

第一步:找到服务器上的 Java 进程

首先登录服务器。

执行:

复制代码
jps -l

例如:

复制代码
[root@server ~]# jps -l
12345 org.apache.catalina.startup.Bootstrap
13579 sun.tools.jps.Jps

这里:

复制代码
12345

就是我们需要分析的 Java 进程 PID。


第二步:检查磁盘空间

Heap Dump 可能非常大。

例如:

复制代码
JVM 最大堆:8G

生成出来的 .hprof 文件可能达到几个 GB。

所以生成之前建议先执行:

复制代码
df -h

确认目标磁盘空间足够。

例如:

复制代码
Filesystem      Size  Used Avail Use%
/dev/sda3       100G   55G   45G  56%

第三步:生成 Heap Dump

现在假设 Java PID 是:

复制代码
12345

推荐使用 jcmd

复制代码
jcmd 12345 GC.heap_dump /data/dump/tongweb.hprof

也可以使用 jmap

复制代码
jmap -dump:format=b,file=/data/dump/tongweb.hprof 12345

生成成功后会得到:

复制代码
tongweb.hprof

这个文件就是后面 MAT 分析的对象。


确认文件是否生成成功

执行:

复制代码
ls -lh /data/dump/tongweb.hprof

例如:

复制代码
-rw------- 1 root root 4.2G Sep 3 13:20 tongweb.hprof

这里重点看文件大小。

如果发现:

复制代码
4.2G

说明这个文件比较大,后续下载和 MAT 分析都需要一定时间。


第四步:把 Heap Dump 下载到本地

服务器上的文件一般不建议直接在服务器上分析。

通常是:

复制代码
服务器
  ↓
生成 hprof
  ↓
下载
  ↓
自己的电脑
  ↓
MAT 分析

例如 Mac/Linux 可以使用:

复制代码
scp root@192.168.1.100:/data/dump/tongweb.hprof .

如果 SSH 使用其他端口:

复制代码
scp -P 2222 root@192.168.1.100:/data/dump/tongweb.hprof .

也可以使用 Xftp、WinSCP、SFTP 等工具传输。


第五步:使用 MAT 打开 Heap Dump

打开 Eclipse Memory Analyzer。

你现在看到的这个界面就是 MAT 的主界面。

然后点击:

复制代码
File
  ↓
Open Heap Dump

选择:

复制代码
tongweb.hprof

也可以直接点击:

复制代码
Open a Heap Dump

然后选择 .hprof 文件。


打开之后先看 Overview

Heap Dump 加载完成后,MAT 会展示整体信息。

这里可以先看看:

复制代码
对象数量
Class 数量
Class Loader
堆内存情况

不用一开始就研究所有数据。

先建立一个概念:

这个 Heap Dump 记录的到底是一个什么样的 JVM 内存现场。


第一项重点分析:Leak Suspects

MAT 通常会提供:

复制代码
Leak Suspects

可以理解成:

MAT 根据当前 Heap Dump 自动找出来的可疑内存占用点。

例如:

复制代码
Problem Suspect 1

One instance of xxx
retains a large amount of memory

这时候可以继续点击进去查看具体对象和引用关系。

不过要注意:

Leak Suspects 标记出来的对象不一定就是内存泄漏。

比如一个正常的大缓存,本身就可能占用几个 GB。

所以还需要继续分析。


第二项重点分析:Histogram

找到:

复制代码
Histogram

Histogram 可以简单理解成:

按照 Java 类统计对象数量和内存占用。

例如:

复制代码
Class Name                  Objects       Shallow Heap
-------------------------------------------------------
java.lang.String            3000000       120 MB
byte[]                      1000000       800 MB
java.util.HashMap$Node      900000         30 MB
com.xxx.User                500000         40 MB

这里重点观察两个问题:

对象数量是不是异常?

例如:

复制代码
User:500万
Order:300万
String:1000万

如果业务实际上只有几十万用户,这就值得调查。

哪些对象占用内存最多?

尤其关注:

复制代码
byte[]
char[]
String
HashMap
ConcurrentHashMap
业务自己的对象

第三项重点分析:Dominator Tree

如果想进一步找:

到底是谁"占住"了大量内存?

可以打开:

复制代码
Dominator Tree

重点关注:

复制代码
Retained Heap

例如:

复制代码
com.xxx.Cache          3.2 GB
HashMap                2.8 GB
com.xxx.UserManager    1.5 GB
byte[]                 900 MB

这时候:

复制代码
com.xxx.Cache

就值得重点检查。

因为它自己可能只占几十 MB,但它引用了大量其他对象,最终导致:

复制代码
Retained Heap = 3.2 GB

这也是 MAT 排查内存问题时非常重要的一个指标。


最后一个关键分析:Path to GC Roots

假设我们发现:

复制代码
com.xxx.User

占用了大量内存。

接下来最关键的问题是:

为什么这些 User 一直没有被 GC 回收?

可以右键对象,找到:

复制代码
Path to GC Roots

然后查看引用链。

例如:

复制代码
GC Root
   ↓
Thread
   ↓
ThreadLocalMap
   ↓
ThreadLocal
   ↓
User

或者:

复制代码
GC Root
   ↓
Static Field
   ↓
Cache
   ↓
HashMap
   ↓
User

这时候就开始接近真正的问题了。

  • exclude all phantom/weak/soft etc. references 👉【排查泄漏首选】只保留强引用,屏蔽缓存类弱引用干扰。
  • include all references:全部引用都展示(会出来大量 WeakHashMap 等弱引用,信息很乱)。

实际排查时主要关注哪些对象?

如果是生产环境 Java 应用,我一般会重点关注这些:

类型 重点检查
HashMap 是否无限增长
ConcurrentHashMap 是否存在无上限缓存
String 数量是否异常
byte[] 是否存在大量数据、文件、请求内容
Thread 是否存在异常线程
ThreadLocal 是否长期持有业务对象
Session 是否保存了大量数据
ClassLoader 是否存在重复部署导致无法释放
业务对象 是否数量异常、长期存活

特别是看到:

复制代码
Retained Heap 很大

不要马上判断是泄漏。

应该继续问:

复制代码
谁引用它?
为什么引用?
这个对象正常情况下应该存在多久?
有没有清理机制?

一个简单的实际案例

假设客户反馈:

TongWeb 运行几天以后内存越来越高,最后 OOM。

我们可以按照下面的方式排查:

最终可能发现:

复制代码
某个 static ConcurrentHashMap
        ↓
不断 put 数据
        ↓
没有过期机制
        ↓
对象越来越多
        ↓
Retained Heap 持续增长
        ↓
Full GC 无法回收
        ↓
最终 OOM

这才是一次比较完整的内存问题排查。


还有一个非常实用的方法:对比多个 Heap Dump

如果问题是:

内存随着运行时间不断上涨

不要只生成一个 Heap Dump。

可以在不同时间分别生成:

复制代码
dump-01.hprof
dump-02.hprof
dump-03.hprof

例如:

复制代码
第一次:JVM 使用 3G
第二次:JVM 使用 5G
第三次:JVM 使用 7G

然后分别使用 MAT 分析。

重点比较:

复制代码
哪些对象越来越多?
哪些对象 Retained Heap 不断增加?
哪些对象始终没有被释放?

这种方式往往比只分析一次 Heap Dump 更容易发现真正的内存泄漏。


小结一下

如果刚开始接触 MAT,不需要一次把所有功能都学会

先记住这几个:

复制代码
Heap Dump
    ↓
Leak Suspects
    ↓
Histogram
    ↓
Dominator Tree
    ↓
Path to GC Roots

分别解决:

复制代码
Heap Dump
→ 保存 JVM 某一时刻的内存现场

Leak Suspects
→ MAT 帮你找可疑点

Histogram
→ 看什么对象最多

Dominator Tree
→ 看谁占住了最多内存

Path to GC Roots
→ 看为什么这些对象一直没有被回收

最终目的不是"看懂 MAT 里的所有数据",而是回答三个问题:

谁占用了内存?
为什么占这么多?
为什么 GC 没有把它回收?

相关推荐
深圳市益普科技有限公司1 小时前
半导体MES的数字孪生:虚拟调试如何把上线风险提前清零
java·开发语言
星栖与芯1 小时前
STM32MP157 M4 指针避坑(三):生命周期与内存踩踏——HardFault 重灾区
java·网络·stm32
Maynor9961 小时前
「原子弹爆炸」级别:Astra 复刻游戏合集(含实机截图)
java·linux·运维·数据库·gpt·游戏
CV艺术家1 小时前
openssL生成免费的证书
java·linux·服务器
全速向光1 小时前
手机玩我的世界Java版:FCL启动器下载使用指南
java·智能手机·游戏程序
pjj198541 小时前
Hadoop-python中使用大数据1
java·ide·eclipse
好好沉淀1 小时前
巧用 Set 去重与复合键:高效统计分组内不重复元素的数量
java·spring boot·后端
Wang's Blog2 小时前
Java框架快速入门: Spring Security+OAuth2之工程结构与开发环境配置
java·spring·状态模式
lzfshub2 小时前
Open-DIS Python发送DIS实体状态PDU:实现坦克炮塔与主炮部件参数
java·网络·python·dis