Skip to content

MAT(堆分析/内存泄漏排查)

MAT:堆转储分析的利器

MAT(Memory Analyzer Tool)是 Eclipse 提供的专业堆转储分析工具,专门用于排查内存泄漏和内存占用问题。它能处理数 GB 的大堆转储,分析能力远超 jhat 和 VisualVM 自带的 HeapWalker。

安装与启动

安装方式

bash
# 方式一:独立安装(推荐)
# 下载地址:https://eclipse.dev/mat/downloads/
# 下载 mat-macosx-cocoa-aarch64_*.tar.gz 或对应版本
tar -xzf mat-macosx-cocoa-aarch64_*.tar.gz
./mat/MemoryAnalyzer

# 方式二:Eclipse 插件
# Help → Eclipse Marketplace → 搜索 "Memory Analyzer"

# 方式三:命令行版本(无需 GUI)
# MAT 也提供命令行工具,适合服务器环境
./ParseHeapDump.sh heap.hprof org.eclipse.mat.api:suspects

内存配置

处理大堆转储时,建议增加 MAT 内存:

bash
# 修改 MemoryAnalyzer.ini
# 内存建议为堆转储文件的 1/3 到 1/2
-vmargs
-Xmx4g        # 根据堆转储大小调整
-XX:+UseG1GC

打开堆转储文件

支持的格式

MAT 支持的堆转储格式:
1. .hprof          → 标准 HPROF 格式(jmap、jcmd 生成)
2. .phd            → IBM Portable Heap Dump
3. .dprof          → DTFJ 格式(IBM JVM)
4. .yarp           → SAP JVM 格式

打开方式

bash
# 方式一:MAT 界面打开
# File → Open Heap Dump → 选择 .hprof 文件

# 方式二:命令行打开
MemoryAnalyzer -hprofPath heap.hprof

# 方式三:指定报告类型
./ParseHeapDump.sh heap.hprof org.eclipse.mat.api:suspects

概览仪表盘(Overview)

打开堆转储后,首先看到的是 Overview 页面:

Overview 页面包含:

1. Leak Suspects → 内存泄漏嫌疑人(自动分析)
2. Top Components → 占用最多的组件
3. Biggest Objects → 最大的对象
4. Actions → 常用分析操作
5. Step-by-Step Tutorials → 引导式分析

Leak Suspects(泄漏嫌疑人)

MAT 的自动分析会列出可疑的内存泄漏区域:

Leak Suspects 报告示例:
---
1. One instance of "com.example.Cache" loaded by
   "<system class loader>" occupies 512,345,678 bytes.
   ...
   → 点击「Details」查看详细信息

2. The thread java.lang.Thread @ 0x12345678 keeps
   causing OutOfMemoryError...
   → 线程持有的对象导致的 OOM

Histogram(直方图)

Histogram 是最基础的视图,显示每个类的实例数量和内存占用。

基本用法

查看方式:
  导航栏 → Java Basics → Histogram

显示内容:
  Class Name        | 对象数量 | Shallow Heap | Retained Heap
  java.lang.String  |   45,678 |    2,678,901 |   15,234,567
  char[]            |   23,456 |    4,567,890 |   10,123,456
  com.example.User  |    1,234 |      456,789 |    2,345,678

关键概念:Shallow Heap vs Retained Heap

Shallow Heap(浅堆):
  → 对象本身占用的内存
  → 包含:对象头 + 实例字段

Retained Heap(深堆):
  → 对象本身 + 所有可达对象的总和
  → 如果对象被 GC,可释放的总内存
  → 包含了对象引用的子树

示例:
  User 对象 → shallow: 100B, retained: 500KB
              (User 对象本身 100B,
               持有的 List 中所有对象 499.9KB)

常用操作

Histogram 常用操作:

1. 按内存排序
   → 点击「Retained Heap」列排序

2. 搜索类
   → 搜索框输入类名

3. 过滤
   → 右键 → Show Objects by Class → with class loader
   → 右键 → List Objects → with incoming references

4. 正则过滤
   → 支持 Java 正则表达式,如 "java\\.util\\..*"

Dominator Tree(支配树)

Dominator Tree 是 MAT 中最重要的视图之一,用于快速定位大内存占用者。

什么是支配关系

支配关系定义:
  对象 A 支配对象 B,当且仅当:
  所有从 GC Root 到 B 的路径都经过 A

支配树的特点:
  → 如果 A 被 GC,所有被 A 支配的对象都会被回收
  → 树的根节点通常是 GC Root
  → 子节点是被父节点直接/间接支配的对象

常用操作

Dominator Tree 视图:

  Class Name                | Retained Heap | Percentage
  com.example.Cache         |   512,345,678 |    45.2%
  └─ java.util.HashMap      |   456,789,012 |    40.3%
     └─ java.util.HashMap$Entry[2048] | 345,678,901 | 30.5%

操作:
1. 按 Retained Heap 排序
2. 展开树节点查看子节点
3. 右键 → View → Path to GC Roots
4. 右键 → Merge Shortest Paths to GC Roots

GC Root 路径分析

Path to GC Roots(到 GC Root 的路径):
  → 从对象出发,追溯到 GC Root 的最短路径
  → 用于判断对象为什么不能被回收

GC Root 类型:
1. Class → 由 Bootstrap ClassLoader 加载的类
2. Thread → 活动线程
3. Stack Local → 栈上的局部变量
4. Monitor → 持有监视器锁的对象
5. Native Static → 静态变量

Top Consumers(最大内存消费者)

Top Consumers 自动列出内存占用的主要区域:

Top Consumers 报告:

1. by Class → 按类统计内存占用
2. by Package → 按包统计
3. by ClassLoader → 按类加载器统计

 Biggest Objects → 按单个对象大小排序
  对象实例                      | Retained Heap
  byte[1024000]                |    1,000,000
  com.example.BigCache          |      500,000
  char[500000]                 |      500,000

OQL(对象查询语言)

OQL 允许用 SQL-like 语法查询堆:

基本语法

sql
SELECT * FROM java.lang.String

SELECT * FROM char[] WHERE length > 1000

SELECT * FROM com.example.MyClass WHERE field != null

常用查询示例

sql
-- 查找所有 String 内容包含 "error" 的实例
SELECT s.value.toString() FROM java.lang.String s
WHERE s.value.toString().contains("error")

-- 查找大于 1MB 的数组
SELECT * FROM array.* WHERE length > 1048576

-- 查找持有大量内存的 HashMap
SELECT * FROM java.util.HashMap
WHERE size() > 10000

-- 查找线程局部变量
SELECT * FROM java.lang.ThreadLocal$ThreadLocalMap

-- 查找特定类加载器加载的所有类
SELECT * FROM java.lang.Class
WHERE classLoader.class.name.toString() = "org.apache.catalina.loader.WebappClassLoader"

-- 查找 String.intern() 过的不该存在的字符串
SELECT * FROM java.lang.String s WHERE s.count > 100

OQL 辅助函数

sql
-- toHexString: 转换为十六进制地址
SELECT toHexString(addr) FROM com.example.MyClass

-- objects to Retained Heap
-- 统计某个类的总 retained heap
SELECT sum(retainedHeap(obj)) FROM "com.example.MyClass"

-- 按 retained heap 排序
SELECT * FROM com.example.MyClass
ORDER BY retainedHeap(this) DESC

-- 查看对象字段
SELECT fields(this) FROM com.example.MyClass

内存泄漏分析实战

场景一:Cache 导致内存泄漏

bash
# 1. 生成堆转储
jmap -dump:live,format=b,file=heap.hprof <pid>

# 2. 用 MAT 打开
MemoryAnalyzer -hprofPath heap.hprof

# 3. 查看 Leak Suspects
# Overview → Leak Suspects → 点击 Details

# 4. 查看 Histogram
# Java Basics → Histogram
# 按 Retained Heap 排序
# 找到 Cache 相关的类(如 ConcurrentHashMap)

# 5. 查看 Dominator Tree
# Java Basics → Dominator Tree
# 展开 Cache 对象
# 查看持有的数据量

# 6. 追溯 GC Root
# 右键 Cache 对象 → Path to GC Roots → exclude weak/soft references
# 分析为什么 Cache 没有被回收:
#  - 静态变量持有?
#  - 线程局部变量持有?
#  - 监听器/回调持有?

# 7. 定位泄漏原因
# 常见原因:
#  - 静态 Map 无上限增长
#  - Listener 没有正确移除
#  - ThreadLocal 没有清理

场景二:线程导致的 OOM

bash
# 1. 查看 OOM 时的堆转储
# 或者:jmap -dump:live,format=b,file=heap.hprof <pid>

# 2. 查看 Leak Suspects
# Overview → Leak Suspects
# 如果报告指出某个线程导致 OOM

# 3. 查看线程
# Java Basics → Thread Overview
# 或:Java Basics → Thread Details

# 4. 查看线程持有的对象
# 选择具体线程 → 查看其 Stack Frames
# 查看局部变量引用的对象

# 5. 分析线程栈
# System Thread → java.lang.Thread
# 查看状态和堆栈

场景三:类加载器泄漏

bash
# 场景:应用反复部署后内存持续增长(类加载器泄漏)

# 1. 查看 Histogram
# 按 ClassLoader 分组
# 右键 → Group By → Class Loader

# 2. 查看重复的类加载器
# 同一类加载器加载的类数量是否过多?

# 3. 查看对象
# 选择类加载器 → List Objects → with outgoing references
# 查看该加载器加载的所有类

# 4. 追溯 GC Root
# 右键 → Path to GC Roots
# 找出什么持有类加载器导致无法卸载

快捷操作

MAT 常用快捷键:
  Ctrl + F     → 查找
  Ctrl + I     → 检查对象
  Ctrl + L     → OQL 控制台
  Ctrl + 1     → Quick Fix
  F5           → 刷新

常用右键菜单:
  Path to GC Roots → 追溯 GC Root 路径
  Merge Shortest Paths → 合并多条路径
  List Objects → 列出对象
  Compare to Another Heap Dump → 对比两个堆转储

本节小结

MAT 核心功能速查:

功能用途入口
Leak Suspects自动分析内存泄漏嫌疑人Overview
Histogram按类统计实例数和内存Java Basics
Dominator Tree按支配关系显示内存占用Java Basics
Top Consumers最大内存消费者排名Reports
OQLSQL-like 堆查询工具栏 OQL
Thread Overview线程及持有对象Java Basics

使用技巧:先看 Leak Suspects 快速定位,再通过 Histogram/Dominator Tree 深入分析,最后用 Path to GC Roots 追溯原因。

下一节,我们来看 JProfiler(安装/内存/CPU/线程分析)

基于 VitePress 构建