
简介这份资源是面向Java开发与性能调优人员的MATMemory Analyzer Tool工具包用于解析JVM生成的hprof堆转储文件定位内存泄漏、内存占用过高等问题。包内共约2000个文件以1891个html帮助文档、217个png界面截图、183个jar核心组件为主辅以xml配置、css样式、properties与class等运行文件并附有示例hprof快照压缩包约75.44MB属于完整的离线安装与文档体系。已有6625人学习下载说明其在内存分析场景中认可度较高。借助其中的帮助文档与示例数据读者可系统了解支配树、泄漏嫌疑人报告、Shallow Heap与Retained Heap区分、OQL自定义查询等核心视图的用法并结合线程堆栈与多次快照对比逐步排查对象生命周期与引用关系问题形成可复用的内存诊断思路。1. 拿到一份 hprof 别急着上 VisualVMMAT 到底解决什么问题线上服务跑着跑着内存曲线一路向上重启就好、过两天又涨运维把 dump 出来的 hprof 文件甩过来几百兆到几个 G用文本编辑器打开全是二进制乱码。这时候多数人第一反应是 VisualVM 或者 jmap -histo 看个类排名但真到要定位「谁持有这个对象、为什么回收不掉」的时候直方图就不够用了。MATMemory Analyzer Tool就是干这个的它把 hprof 解析成对象图算出支配树、保留集、泄漏嫌疑报告让你从「哪个类占内存多」走到「哪条引用链把它钉在堆里」。这份资源适合正在排查 OOM、内存缓慢泄漏、缓存膨胀的 Java 后端和 Android 开发者也适合需要给线上事故写复盘材料的人。它不负责压测、不负责监控只负责把一份静态堆快照拆开给你看。2. 把 hprof 喂进 MAT解析流程与支配树原理2.1 hprof 里到底存了什么为什么不能直接读hprof 是 JVM 在某一时刻的堆快照本质是一段带标记的二进制流。它记录了两类东西一是所有存活对象的类信息、字段值、数组内容二是对象之间的引用关系。注意「存活」两个字——dump 触发时通常会先做一次 Full GC除非用-XX:HeapDumpBeforeFullGC之类的参数调整时机所以快照里基本是 GC 之后还活着的对象这也意味着你看到的内存占用是「回收不掉的」而不是「曾经分配过的」。正因为存的是对象图而不是文本直接 grep 是没意义的。MAT 做的第一件事是解析这段二进制在内存里重建一张有向图节点是对象边是引用。几百兆的 hprof 解析后可能膨胀到几个 G 的堆内结构所以 MAT 自己也要吃内存这一点后面避坑章节会专门讲。理解对象图之后两个核心概念必须立住否则后面看报表全是懵的Shallow Heap浅堆对象自身占的字节数不含它引用的对象。一个String的浅堆就是对象头加字段跟它指向的char[]或byte[]无关。Retained Heap保留堆如果把这个对象干掉能连带回收多少内存。这个值才是排查泄漏时真正要看的因为它回答的是「释放它能省多少」。而 Retained Heap 的计算依赖支配树Dominator Tree。定义很直白从 GC Root 出发到对象 B 的所有路径都必经对象 A那 A 就支配 B。支配树把这种关系整理成树形父节点的保留堆包含子节点。于是你打开支配树按 Retained Heap 降序排排在最上面的往往就是「罪魁」——一个不起眼的HashMap$Node或者ThreadLocalMap$Entry保留堆几个 G。2.2 从 dump 到第一张报表的操作路径拿到 hprof 后标准动作是先在服务器上确认文件完整性再拉回本地分析。dump 本身用 jmap 或 jcmd 都行生产环境注意别在业务高峰做Full GC 加写盘会带来明显停顿。# 方式一jmappid 换成实际进程号formatb 表示二进制 hprof jmap -dump:live,formatb,file/tmp/heap-$(date %Y%m%d%H%M).hprof pid # 方式二jcmd新版本 JDK 更推荐GC.heap_dump 同样支持 live 选项 jcmd pid GC.heap_dump /tmp/heap-$(date %Y%m%d%H%M).hprof # dump 完先看大小和头部确认不是 0 字节或截断文件 ls -lh /tmp/heap-*.hprof file /tmp/heap-*.hproflive参数表示只 dump 存活对象会先触发一次 GC代价是停顿更长但文件更小、噪声更少去掉live则连待回收对象一起 dump适合分析「为什么这些对象还没被回收」的时序问题。file路径要确保磁盘有足够空间经验值是堆大小的 1 到 1.5 倍。file命令能识别出Java heap dump字样就说明头部没坏如果显示 data 或者报错多半是 dump 过程中进程被杀或磁盘写满。文件拉回本地后MAT 打开时有两个选项容易选错一是「Keep unreachable objects」默认不勾勾了会把待回收对象也纳入分析文件解析更慢、内存占用更高只在怀疑对象被提前回收或者分析 finalizer 相关问题时才用二是解析时的 heap size 设置MAT 自己的MemoryAnalyzer.ini里-Xmx要给到 hprof 大小的 1.5 到 2 倍否则大文件直接解析失败。打开后先别急着点 Leak Suspects先看 Overview 里的饼图确认整体分布再进 Histogram 按类看实例数和浅堆最后切到 Dominator Tree 按保留堆排序。这个顺序的逻辑是先看总量对不对得上监控再看哪类对象数量异常最后定位到具体实例和引用链。Leak Suspects 是自动生成的猜测报告适合快速给方向但它基于启发式规则误报率不低不能当结论用。2.3 用 OQL 和引用链把「谁持有它」问清楚支配树告诉你「谁占得多」但排查泄漏真正要回答的是「为什么它回收不掉」。这时候两个功能是主力Path to GC Roots 和 OQL。在支配树或直方图里选中一个对象右键Path to GC Roots→exclude weak/soft referencesMAT 会列出从 GC Root 到该对象的引用链。排除弱引用和软引用很关键因为这两类引用本身不阻止回收混进来只会干扰判断。常见的泄漏链长这样Thread→ThreadLocalMap→Entry→value→ 你的大对象。看到这条链基本就能断定是 ThreadLocal 没 remove线程池复用线程导致 value 一直挂着。OQL 则是 MAT 内置的类 SQL 查询语言适合按条件筛对象。比如想找出所有超过 1MB 的byte[]-- OQL查所有长度大于 1MB 的 byte 数组按长度降序 SELECT x FROM byte[] x WHERE x.length 1048576 ORDER BY x.length DESCbyte[]是类名length是数组长度的内置属性1048576就是 1MB。OQL 的语法接近 SQL 但只支持有限的表达式不能做 join复杂逻辑要靠SELECT * FROM INSTANCEOF配合条件。实际用的时候我更常写的是按类名加字段条件筛比如查某个缓存类里 size 字段异常大的实例这比翻支配树快得多。提示OQL 查询大堆时可能跑很久先在直方图里缩小类范围再写查询别一上来就SELECT * FROM java.lang.Object。3. 三类高频场景泄漏、缓存膨胀、OOM 现场怎么读3.1 缓慢泄漏对比两份 dump 找增长点单份 dump 只能看到「现在谁占着」看不出「谁在涨」。缓慢泄漏的标准打法是间隔一段时间 dump 两次用 MAT 的Compare Basket或者 Histogram 对比功能看增量。操作上把两份 hprof 都打开在 Histogram 视图里选中两个快照做 diffMAT 会给出每个类的实例数变化和浅堆变化。判断要点是看实例数增长而不是只看字节数。字节数受对象大小波动影响实例数持续单调增长才是泄漏的强信号。比如HashMap$Node从 10 万涨到 30 万基本可以锁定某个 Map 只进不出。找到增长类之后回到单份 dump 里对该类的实例做 Path to GC Roots看它们挂在哪个容器上。这里有个容易翻车的点两次 dump 之间如果发生过业务高峰正常缓存也会涨不能一律当泄漏。我的习惯是选两个业务量相近的时间点或者结合监控确认内存曲线是「阶梯上升不回落」而不是「高峰涨、低谷降」。前者是泄漏后者多半是缓存容量没设上限。3.2 缓存膨胀区分「该回收」和「不该回收」缓存类问题最迷惑人因为对象确实「有用」只是太多了。典型现象是支配树顶部是一个ConcurrentHashMap保留堆占了大半展开后全是业务对象。这时候要问的不是「为什么回收不掉」而是「为什么放了这么多」。排查路径是先看这个 Map 的容量和元素数再看元素的 key 分布。如果 key 是用户 ID 且数量等于全量用户数那就是把缓存当存储用了没有淘汰策略。如果 key 带时间戳后缀说明每次请求都生成新 key旧 key 永不命中也不清理这是典型的 key 设计错误。MAT 里可以用 OQL 统计某个 Map 的 size 字段或者直接看该 Map 实例的table数组长度和实际节点数对比负载因子异常高说明扩容过但没缩容。常见做法是给缓存加容量上限加过期时间但排查阶段先别急着改代码先用 dump 证明「元素数确实无界增长」否则改完还是漏。我见过一个案例缓存本身有 LRU但 key 里混进了请求 ID导致命中率接近零、元素数只增不减光看代码根本发现不了是 dump 里 key 的字符串模式暴露的。3.3 OOM 现场从异常栈反推 dump 时机OOM 发生时 JVM 可以自动 dump参数是-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/path。这个组合建议所有生产服务都加上它是事后分析的后悔药。但要注意自动 dump 的时机是在 OOM 抛出之后此时堆已经接近满dump 过程本身可能因为空间不足失败所以HeapDumpPath要指向独立磁盘分区。拿到 OOM 现场的 dump第一件事是看 OOM 的类型。Java heap space是堆满看支配树找大对象GC overhead limit exceeded是 GC 占 CPU 过高但回收效果差本质还是堆里活对象太多Metaspace或unable to create new native thread则不是堆问题MAT 帮不上忙别浪费时间。对于Java heap space我的顺序是先看 Histogram 里实例数最多的类再看支配树保留堆最大的对象两者往往指向同一个东西。如果支配树顶部是一个巨大的数组展开看它的引用链常见的是「一次性把全表查出来」或者「日志拼接用了超大 StringBuilder」。这类问题的特征是单个对象就占几个 G非常好认。注意自动 dump 的文件可能因为 OOM 时堆状态不稳定而解析失败如果 MAT 报Unknown HPROF Version或解析中途崩溃先确认文件大小是否等于预期必要时用jhat或其它工具交叉验证文件完整性。4. 避坑清单MAT 用错比不用更浪费时间4.1 现象MAT 打开大文件直接卡死或报 OutOfMemoryError原因MAT 自身是 Java 应用解析 hprof 时要在自己的堆里重建对象图默认-Xmx往往只有 1G 左右几个 G 的 hprof 根本放不下。解决改 MAT 安装目录下的MemoryAnalyzer.ini把-Xmx调到 hprof 大小的 1.5 到 2 倍比如 4G 的 dump 给 8G。同时确认系统物理内存够否则会触发 swap 更慢。如果机器内存实在不够用 MAT 的「Parse heap dump with a temporary file」选项把中间结构落盘代价是速度慢很多。4.2 现象支配树里排第一的对象代码里根本找不到对应的大对象原因支配树按保留堆排序而保留堆包含被支配的所有子对象。排第一的可能是个容器或者线程对象真正的大头在它下面。解决展开该节点逐层看或者直接对该节点做Path to GC Roots反查。另一个可能是对象被多个引用共享支配关系把它归给了其中一个父节点实际业务含义要看引用链而不是树形位置。4.3 现象Leak Suspects 报告说某处泄漏改完内存还是涨原因Leak Suspects 是基于规则的启发式猜测它会把「占用大」当成「可疑」但占用大不等于泄漏。比如一个预热好的本地缓存本来就该占那么多。解决把 Leak Suspects 当线索不当结论必须用两份 dump 对比确认增长再用引用链确认回收路径被谁阻断。单份 dump 加自动报告就下结论是翻车高发区。4.4 现象Path to GC Roots 列出的链里全是弱引用看不出真实持有者原因默认查询包含了所有引用类型弱引用、软引用、虚引用都会出现在链里但它们不阻止回收混进来只会让链变长变乱。解决右键菜单里选exclude weak/soft references只看强引用链。如果排除后链断了说明这个对象本来就可回收不是泄漏问题在别处。4.5 现象dump 文件拉回本地后解析报错服务器上明明 dump 成功了原因传输过程截断或者 dump 时磁盘写满导致文件不完整。解决先在服务器上ls -lh看大小是否合理用md5sum比对传输前后必要时重新 dump 到空间充足的分区。另外注意 dump 过程中如果进程被 kill文件头可能完整但尾部缺失MAT 解析到一半才报错这种最难查所以 dump 完立刻校验大小是必要动作。5. 进阶用 MAT 的报表导出和脚本化把复盘做扎实排查完不是结束事故复盘需要可复现的证据。MAT 支持把支配树、直方图导出成 CSV也支持用ParseHeapDump命令行模式批量处理这对需要定期分析多份 dump 的场景很实用。命令行解析的基本用法是调用 MAT 自带的批处理入口指定 hprof 和要生成的报表# 用 MAT 的 ParseHeapDump 命令行模式解析并生成支配树和泄漏报告 # 路径按实际安装位置调整-keep_unreachable_objects 不加表示只分析存活对象 ./ParseHeapDump.sh /data/heap-20250101.hprof \ org.eclipse.mat.api:suspects \ org.eclipse.mat.api:overview \ org.eclipse.mat.api:top_components这条命令会在 hprof 同目录生成若干 zip 报表suspects是泄漏嫌疑overview是总览top_components是按组件聚合的占用。参数里的org.eclipse.mat.api:前缀是 MAT 的报表扩展点标识后面跟报表名。批处理模式适合放进定时任务比如每天凌晨对当天 dump 跑一遍把报表归档出问题时直接翻历史。注意命令行模式同样受MemoryAnalyzer.ini的堆设置影响大文件要提前调大。另一个实用技巧是自定义「组件」聚合。MAT 允许把一组类归到一个组件里比如把所有业务 DTO 归为「业务对象」、所有框架类归为「框架开销」这样 Overview 里的饼图能直接回答「内存到底被谁吃了」这种给非技术同事看的问题。配置入口在Window→Preferences→Memory Analyzer→Grouping按类名正则分组。我一般会先按包名粗分再对占用最高的包细分到类。验证分析结论是否靠谱有个笨但有效的办法改完代码后重新 dump 一份用同样的 OQL 或直方图对比关键类的实例数确认增长停了。别只看监控曲线降了就说修好了曲线受 GC 时机影响会骗人dump 里的实例数不会。从那以后我每次拿到 hprof都强制自己先做两件事确认文件完整性、调大 MAT 堆再打开然后才允许自己点任何报表。这个习惯救过我好几次因为解析阶段翻车浪费的时间比分析本身还多。希望帮到你。本文还有配套的精品资源点击获取