
简介MemoryAnalyzer 1.6.12016年11月25日发布Windows 32/64 位版本面向需要排查 Java 应用内存问题的开发者与测试人员。它基于 Eclipse MAT通过解析堆转储文件定位内存泄漏提供支配树、泄漏嫌疑报告、直方图与引用链分析等视图帮助判断哪些对象占用大量内存、为何无法被回收适用于性能调优与线上问题诊断场景。压缩包共 463 个文件约 57.99MB以 jar 插件与依赖库、png/gif 界面图标、html/css 帮助文档与样式、xml/properties 配置及签名文件为主另含 exe 启动程序、bat 脚本与说明文本解压后可直接运行。已有 303 人学习下载。借助内置说明与可执行文件读者能快速搭建分析环境结合各类视图完成泄漏定位与引用链追踪形成从堆快照到问题根因的排查思路。1. 堆转储分析工具为什么一个 2016 年的老包还值得留在工具箱里线上服务突然 OOM重启后一切正常日志里只剩一行java.lang.OutOfMemoryError: Java heap space再无其他线索。这时候真正能救命的不是监控大盘而是事发前抓下来的那份heap dump。Memory Analyzer简称 MAT就是啃这份二进制黑匣子的工具它能算出谁占了最多内存、对象之间谁引用谁、哪条引用链让本该回收的对象活了下来。这个MemoryAnalyzer-1.6.1.20161125-win32.win32.x86_64.zip是 Windows 64 位免安装包解压即用不需要装 JDK 之外的任何东西。适合谁适合手上已经有 hprof 文件、需要在一小时内给出「内存泄漏根因」结论的后端和中间件同学。老版本不代表不能用关键是你要知道它的边界在哪。2. 解压即用从 hprof 到第一张支配树报表2.1 包结构与启动方式这个 zip 解压后是一个mat目录核心是MemoryAnalyzer.exe、plugins/、configuration/和MemoryAnalyzer.ini。它是基于 Eclipse RCP 构建的所以启动慢、界面重但换来的是成熟的视图体系。启动前先确认本机有 64 位 JREMAT 自身也是 Java 程序MemoryAnalyzer.ini里的-Xmx决定它能吃多大的堆转储——默认值往往偏小分析 4G 以上的 dump 会直接抛OutOfMemoryError在 MAT 自己身上这点后面避坑章细说。启动方式有两种双击 exe或者命令行带参数直接打开文件。批量场景我更倾向命令行省去每次点「File → Open Heap Dump」# Windows 下用命令行直接打开指定 dump并指定工作区避免污染默认工作区 MemoryAnalyzer.exe -data D:\mat-workspace ^ -vm C:\Program Files\Java\jre1.8.0_202\bin\javaw.exe ^ D:\dumps\order-service-20240115.hprof-data指定工作区目录MAT 会在里面存索引和解析缓存一个 dump 动辄生成几百 MB 的索引文件放系统盘容易把 C 盘塞满。-vm显式指定 JVM避免机器上装了多个 Java 版本时 MAT 挑错。最后的路径就是待分析的 hprof。第一次打开大文件会经历「解析 → 建索引 → 计算支配树」三个阶段进度条走完之前别急着操作中途点按钮容易让索引算一半。2.2 读懂 Overview 上的三个数字文件打开后落在 Overview 页最该先看的是三块Heap 大小、Classes 数量、以及那个饼图里的 Top Consumers。Heap 大小告诉你这份 dump 抓取时堆用了多少和-Xmx对比能判断是不是真的打满Classes 数量异常膨胀比如几十万个类通常指向动态代理或类加载器泄漏而不是普通对象堆积。真正干活的是「Histogram」和「Dominator Tree」两个视图。Histogram 按类聚合回答「哪种对象最多」Dominator Tree 按支配关系聚合回答「谁真正持有这些内存」。新手常犯的错是盯着 Histogram 里byte[]排第一就下结论其实byte[]只是被 String、缓冲区、集合间接持有的载体得顺着支配树往上找持有者。我的习惯是先看 Dominator Tree 的前 20 行找到占比最大的那个业务对象再右键「Path to GC Roots」排除弱引用和软引用看强引用链断在哪。2.3 用 OQL 精确定位可疑集合界面点选适合快速定位但要给出可复现的结论OQLObject Query Language更靠谱。它语法接近 SQL能直接对堆里的对象做查询。比如怀疑某个缓存 Map 无限增长可以这样查-- 查询所有 HashMap 实例按内部表大小降序找出异常膨胀的缓存 SELECT h, h.table.length AS bucketCount FROM java.util.HashMap h WHERE h.table ! null ORDER BY bucketCount DESCh.table.length是 HashMap 底层数组长度正常业务缓存几千到几万如果出现几十万甚至上百万的 bucket基本可以锁定是没设上限的本地缓存。再配合SELECT * FROM INSTANCEOF com.example.OrderCache这类按类名过滤的写法能直接把嫌疑对象捞出来。OQL 的坑在于类名必须写全限定名且混淆过的包名要按实际字节码里的名字写写错了不报错、只返回空结果很容易误判成「没有泄漏」。3. 引用链与泄漏嫌疑把「谁持有」这件事查到底3.1 Path to GC Roots 的正确用法「Path to GC Roots」是 MAT 最核心的功能但默认选项会骗人。右键对象选它时弹出的对话框里有「exclude weak/soft references」等勾选项。分析内存泄漏必须勾上排除弱引用、软引用、虚引用否则你会看到一堆WeakReference链误以为对象是被弱引用持有的——实际上弱引用不阻止回收真正的凶手是那条强引用链。操作路径是Dominator Tree 里选中可疑对象 → 右键 → Path to GC Roots → exclude all phantom/weak/soft references。结果会以树形展示从 GC Root线程栈、静态变量、JNI 引用等到目标对象的完整路径。重点看链条中间那些「业务类」节点比如某个ThreadLocalMap的 Entry、某个静态ConcurrentHashMap的 value。找到之后问题往往就变成「这个静态集合为什么只增不减」回到代码里一搜就有答案。3.2 Leak Suspects 报告的可信边界MAT 自带「Leak Suspects」自动分析会生成一份 HTML 报告给出几个「泄漏嫌疑」。这东西对新手友好但别全信。它的启发式规则基于支配树占比和引用模式遇到框架层的大对象比如连接池、序列化缓冲区经常误报成泄漏。我的用法是把它当「线索清单」而不是「结论」报告里每个 suspect 点进去看它给的引用链如果链条终点是框架内部的池化对象且数量稳定基本可以排除如果终点是业务代码里一个无界集合那才是真凶。一个典型场景报告说某个byte[]数组占 40% 堆点进去发现是org.apache.catalina的缓冲区。这时候别急着改 Tomcat 配置先看这个缓冲区是不是被某个未关闭的ServletOutputStream持有——真正的泄漏点可能在业务代码忘了 flush/close缓冲区只是受害者。这种「报告指东、根因在西」的情况靠的就是顺着引用链多走两步。3.3 对比两份 dump 找增长点单份 dump 只能看到「现在谁大」看不出「谁在涨」。生产上更有效的手段是间隔一段时间抓两份 dump用 MAT 的「Compare Basket」功能做差集。操作是把两份 Histogram 都加入 Compare Basket然后执行比较MAT 会列出每个类的实例数增减。增长量最大的那几类就是泄漏的直接证据。# 抓两份 dump 的常用命令间隔 10 分钟注意 jmap 会 STW低峰期执行 jmap -dump:formatb,fileD:\dumps\svc-t1.hprof pid # 等待 10 分钟观察业务量无明显变化后再抓第二份 jmap -dump:formatb,fileD:\dumps\svc-t2.hprof pidformatb表示二进制格式MAT 只认这个file是输出路径。注意jmap -dump会触发一次 Full GC 级别的停顿大堆上可能停几秒到十几秒务必在低峰期做且确认-XX:HeapDumpOnOutOfMemoryError没配的话手动抓是唯一补救手段。两份 dump 的堆大小要接近才有可比性如果第二份抓的时候业务已经恢复、堆被回收了差集会失真。4. 避坑与排查MAT 用起来最容易被绊的五个地方4.1 现象MAT 自己 OOM打不开 dump原因MemoryAnalyzer.ini里的-Xmx默认只有 1G 左右而解析 dump 时 MAT 需要在内存里建索引索引大小通常是 dump 的 1/3 到 1/2。一个 6G 的 dump 配 1G 堆必然在解析阶段就崩。解决编辑MemoryAnalyzer.ini把-Xmx调到 dump 大小的 1.5 倍以上比如 8G dump 配-Xmx12g。同时确认启动用的是 64 位 JVM32 位 JVM 无论怎么配都突破不了 4G 上限。改完重启 MAT别在旧进程上重试。4.2 现象Histogram 里byte[]永远排第一看不出业务问题原因几乎所有字符串、序列化结果、网络缓冲区底层都是byte[]它天然是数量最多的类。盯着它看等于没看。解决切到 Dominator Tree或者用 OQL 按业务类名过滤。另一个技巧是在 Histogram 里右键byte[]选「Merge Shortest Paths to GC Roots」看这些数组分别被哪些业务对象持有往往能一次暴露出几个不同的持有者。4.3 现象Path to GC Roots 显示对象被弱引用持有但内存就是不降原因没勾选排除弱引用/软引用或者对象同时被强引用和弱引用持有你只看到了弱引用那条链。解决重新执行 Path to GC Roots 并勾上排除选项。如果排除后仍然存在强引用链说明确实有强持有如果排除后链条消失那这个对象本来就不该被算作泄漏问题在别处。这个勾选项是分析准确性的分水岭血泪经验是每次都要手动确认。4.4 现象OQL 查询返回空但明明有这类对象原因类名写错最常见的是漏了包名、用了业务别名而非字节码里的全限定名或者类被类加载器隔离导致同名类有多个版本。解决先在 Histogram 里搜类名关键字复制它显示的全限定名到 OQL 里。如果存在多个类加载器加载的同名类OQL 会分别匹配必要时加INSTANCEOF配合类加载器条件过滤。别凭记忆手写类名。4.5 现象分析结论和代码对不上怀疑 MAT 算错原因dump 抓取时机和代码版本不一致或者 dump 是 Full GC 之前抓的里面包含大量待回收对象支配树把这些「垃圾」也算进了持有关系。解决确认 dump 对应的代码 commit 和线上一致抓 dump 前先触发一次 Full GCjmap -histo:live pid会触发再抓能过滤掉待回收对象结论更干净。另外 MAT 的支配树计算基于对象图快照本身不会算错对不上通常是「快照时刻」和「你以为的时刻」有偏差。5. 进阶技巧用 MAT 的批处理接口把分析变成流水线单次手工分析解决的是「这一次」真正省时间的是把常见判断固化成脚本。MAT 提供了基于 Eclipse 的批处理入口可以在无界面模式下跑预定义的解析和报告生成适合接到 dump 自动采集之后做初筛。# 无界面模式解析 dump 并生成 Leak Suspects 报告输出到指定目录 MemoryAnalyzer.exe -consolelog -application org.eclipse.mat.api.parse ^ D:\dumps\svc-t1.hprof ^ -vm C:\Program Files\Java\jre1.8.0_202\bin\javaw.exe-application org.eclipse.mat.api.parse是 MAT 暴露的解析应用入口跑完会在工作区生成索引和基础报告-consolelog把日志打到控制台方便判断是否解析成功。实际落地时我会再包一层dump 文件落到固定目录后用一个批处理脚本遍历新文件、逐个调用上面的命令、把生成的报告 HTML 归档到按日期命名的文件夹第二天上班直接看报告标题里有没有「Leak Suspects」字样有就点进去复核。这里有个参数上的取舍值得说清楚。批处理解析同样受MemoryAnalyzer.ini的-Xmx约束流水线机器如果内存有限宁可串行处理也不要并发跑多个 MAT 实例否则几个大 dump 同时解析会把机器拖垮。我的习惯是给流水线单独配一份 ini堆上限按「最大单份 dump × 1.5」设并且限制同时只跑一个任务。验证分析结论是否靠谱我一般做一次反向确认按引用链找到的持有者回到代码里搜它的写入点确认是否存在「只 put 不 remove」或「key 无界增长」的模式如果代码里确实有对应的清理逻辑那就要怀疑是不是清理条件写错、或者异常路径跳过了清理。这一步能把「MAT 说它大」变成「代码证明它该被回收却没回收」结论才站得住。从那以后我每次拿到 dump都强制先看 Dominator Tree 前 20 行、再跑一次排除弱引用的 Path to GC Roots最后用两份 dump 的差集交叉验证三步走完才敢在群里回「根因是某某缓存没设上限」。这套流程不花哨但能挡住大部分误判。希望帮到你。本文还有配套的精品资源点击获取