资讯动态

MAT 1.6.1 Windows版:Java堆Dump与OOM泄漏排查

发布时间:2026/10/27 19:11:55 来源:尧图企业网站定制
简介MemoryAnalyzer 1.6.12016年11月25日版Windows 64 位压缩包面向需要排查 Java 应用内存泄漏、优化堆内存占用的开发、测试与运维人员。工具围绕堆转储分析展开提供支配树、直方图、泄漏嫌疑报告和引用链追踪等视图可帮助定位大对象、异常增长的类以及无法被回收的引用路径适合具备一定 JVM 基础的开发者用于线上问题复盘与性能诊断。包内共 463 个文件以 jar 组件、png/gif 界面资源、html 帮助页、xml 与 properties 配置为主另有 exe、dll、bat 等启动与运行文件压缩包约 57.99MB目录与依赖较完整。已有 302 人学习下载对希望掌握 MAT 核心分析流程、理解堆转储阅读方法并积累内存问题排查思路的读者这份工具包可作为较稳定的离线参考环境也便于对照帮助文档熟悉各视图用途。1. 解压一个 2016 年的 zip换来半夜排查 OOM 的底气凌晨两点某公司一个后台服务被系统的 OOM 机制干掉好在启动参数里带了-XX:HeapDumpOnOutOfMemoryError留下一个 4GB 的 hprof。用 jvisualvm 打开转圈十分钟还没把对象读完换成 Memory Analyzer下称 MAT索引建完、支配树一排序retained 占 2.6GB 的那个 ConcurrentHashMap 直接排第一屏。这个 zip 就是 MAT 的 Windows 64 位构建版本 1.6.1打包时间戳 2016-11-25解压即用、绿色免安装。它解决的是拿到 dump 之后定位内存占用来源的问题抓取 dump 本身不在本文范围内。适合日常要处理 Java 服务内存问题的人也适合手上只有一台离线 Windows 机器、dump 不能外传的场景。2. 拆包先看结构Eclipse RCP 目录树与 hprof 的解析链2.1 它是一棵目录树不是一个安装程序解开 zip看到的是一副很典型的 Eclipse RCP 产品形态MemoryAnalyzer.exe、MemoryAnalyzer.ini、plugins/、features/、configuration/、artifacts.xml。.exe只是启动器launcher真正在干活的是plugins/下那一堆 OSGi bundleorg.eclipse.mat.*开头的几个 jar 才是分析引擎本体。理解这一点后面很多现象就有解释了改内存要动 ini 而不是改快捷方式换 JDK 要在 ini 里写-vm启动失败时先看启动器有没有把 JVM 拉起来再看 workspace 下.metadata/.log。几个直接后果值得先记住同一个盘上可以并存多个版本1.6.1 和老版本各放一个目录互不影响用哪个点哪个。解压路径不要带中文和空格D:\tools\mat161这种最省事。启动器对含空格路径的处理在部分场景下会出问题。别塞进Program Files。那里受 UAC 管控MAT 默认要在自己的 workspace 写索引和临时文件权限问题会表现成各种莫名其妙的启动报错。整个目录拷给同事对方机器上装好 JDK 8 就能跑这一点在离线环境里非常实用。2.2 hprof 里存的是图不是一张内存快照照片很多人第一次看 Histogram会觉得String 有 200 万个那就是字符串泄漏。这个推论经常是错的原因在文件格式。hprof 是二进制流里面由若干 record 组成字符串表、类 dump类名、父类、字段名与类型、静态字段值、实例 dump类 id 各字段值、对象数组 dump、基本类型数组 dump、GC Root 记录栈帧局部变量、JNI 引用、静态字段、监视器、类加载器、线程栈片段。MAT 读完这些记录在内存里建一张有向图节点是对象边是引用根是 GC Root 集合。三个指标必须分清指标含义用途Shallow Heap对象自身占用的字节含对象头和字段不含它引用的对象判断单个对象大小容易误判Retained Heap该对象被回收后能连带释放掉的总字节判断谁在吃内存主看这个Dominator从 GC Root 到某对象的路径全部经过 A则 A 支配该对象决定 Retained 的归属关系一个 32 字节的HashMap$Node本身没多少但它挂着的 value 可能是个几十 MB 的 byte 数组——Shallow 看着人畜无害Retained 才是真实的账。所以第一眼永远先看 RetainedShallow 只在怀疑单个大对象比如一个超大数组时才拿来做二次验证。2.3 1.6.1 这条分支能跑在哪、卡在哪这是 2016 年底的构建几个边界最好在动手前就知道JVM 基线是 JDK 8。配套的 Equinox 启动器也是那个年代的产物用 JDK 11/17 直接去启很常见的现象是窗口闪一下就没了或者命令行里刷NoClassDefFoundError。这不是包坏了是老启动器不认新 JVM 的模块化布局。手上备一个 JDK 8是最省事的组合。读 dump 的兼容性有代差。JDK 8 及更早版本 HotSpot 抓出来的标准 hprof 最稳。JDK 11 之后抓的 dump用这个版本打开偶尔会中途报解析异常或者对象数对不上。真遇到了别在这里死磕换更新的 MAT 版本去读——版本跟着 dump 的产出环境走这条经验比记具体版本号有用。Android 的 .hprof 要先转换。Dalvik 的 hprof 虽然也叫 hprof头部和 record 语义不同直接拖进去大概率报格式错误得先用hprof-conv转成标准格式。功能面上这个版本是齐的Histogram、Dominator Tree、Leak Suspects、OQL、Compare Basket、HTML 报表导出都在日常定位一个内存泄漏不需要更多东西。3. 跑起来解压、绑 JVM、调 -Xmx 与命令行出报告3.1 解压位置与两种启动入口先解压到一个固定目录比如D:\tools\mat161确认目录里能看到MemoryAnalyzer.exe和MemoryAnalyzer.ini。启动有两个入口用途不同# 入口一图形界面日常排查用 D:\tools\mat161\MemoryAnalyzer.exe # 入口二带控制台输出启动闪退时用它看真实报错 D:\tools\mat161\MemoryAnalyzer.exe -consoleLog -console命令行里直接双击 exe 的好处是JVM 拉不起来、启动器加载 bundle 失败这类问题控制台窗口不会一闪而过报错能看全。图形界面第一次启动会让你选 workspace 目录选一个剩余空间大的盘——workspace 里会存偏好设置和日志小于 1GB 可用空间的盘不用考虑。3.2 MemoryAnalyzer.ini 里必须改的三处用文本编辑器打开MemoryAnalyzer.ini内容大致长这样launcher 的 jar 文件名跟着构建走以你真解出来的为准别照抄-startup plugins/org.eclipse.equinox.launcher_1.3.201.v20161025-1711.jar --launcher.library plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.401.v20161122-1740 -vm C:/Java/jdk1.8.0_202/bin/javaw.exe -vmargs -Xmx4096m -Xms1024m三处改动每一处都是踩出来的第一处-vm和它下面那一行路径。这条是告诉启动器用哪个 JVM必须写在-vmargs之前路径单独占一行不能和-vm挤在同一行。路径用正斜杠/或者双反斜杠\\单反斜杠会被当转义吃掉。指向的必须是javaw.exe而不是java.exe——后者会额外弹一个控制台窗口挂在界面上。如果这个 JDK 是 32 位或者路径写错启动时会报Failed to load the JNI shared library这个报错信息跟内存不够看起来毫无关系实际是位数不匹配。第二处-Xmx。默认值是 1024m处理几百 MB 的小 dump 够用上到 GB 级就不行了典型表现是解析到一半弹An internal error occurred during: Parsing heap dump或者干脆Java heap space。经验值是设为 dump 文件大小的 0.51.0 倍之间设置过大GC 时间会显著增加。但注意MAT 读 dump 用的是内存映射mmap不是把整个文件读进堆所以堆不用等于文件大小——真正吃内存的是建索引和算 retained size 的过程。第三处-Xms。设成和-Xmx一样能减少堆反复扩张的开销对这种启动就猛吃内存、跑完就退出的程序收益明显。-Xmx和-Xms中间不要留空行-vmargs之后的内容全部原样传给 JVM。3.3 命令行批量出报告ParseHeapDump 的实际用法图形界面适合边看边推断但有两种场景更适合走命令行一是 dump 在客户机器上、你只想拿一份 HTML 报告回来二是要一次跑好几个 dump。MAT 自带批量脚本cd /d D:\tools\mat161 ParseHeapDump.bat D:\dump\app-20240115.hprof ^ org.eclipse.mat.api:suspects ^ org.eclipse.mat.api:overview ^ org.eclipse.mat.api:top_components参数拆开看第一个参数是 dump 的绝对路径后面跟的是报表任务的 ID可以有选择地给任务 ID产出什么时候用org.eclipse.mat.api:suspectsleak-suspects.zip HTML只想快速看有没有明显嫌疑对象org.eclipse.mat.api:overviewsystem-overview.html要给不装 MAT 的人看整体情况org.eclipse.mat.api:top_componentstop_components.zip看最大的几个组件/类加载器输出文件落在 dump 同目录。这里有个实用技巧给同事的往往不是 dump 而是leak-suspects.zip解开后的那几个 HTML对方用浏览器就能看展开对象树也能点。JVM 参数依然由MemoryAnalyzer.ini决定在 bat 后面追加-vmargs -Xmx8g容易被启动器吞掉稳妥做法是直接改 ini别在这上面省事。3.4 索引文件既是缓存也是坑第一次打开一个 dumpMAT 会在旁边生成一批索引文件之后再用就快得多文件作用能否删除dump.index解析元数据、对象地址索引可以重开会重建dump.o2hprof.index对象地址到文件位置的映射可以dump.a2hprof.index数组对象的地址映射可以dump.o2ret.index/.a2ret.indexretained size 索引最耗时可以但重建可能要几十分钟dump.threads线程与栈帧信息可以索引是跟着文件名走的。如果把新 dump 覆盖到同名旧文件上MAT 有可能继续用旧索引结果就是对象数对不上、点进去报错、retained 数值离谱。这种情况下一律把dump.*.index和dump.threads全删掉重来。更省心的习惯是dump 文件名带上时间戳和进程号永远不覆盖。还有一个容易被忽略的点dump 放在网络共享盘或移动硬盘上时mmap 的随机读会慢到让人以为卡死。先拷到本地 SSD 再打开同时确认所在分区留出 dump 大小 1/3 以上的空闲空间给索引。4. 四个视图读出结论Histogram、Dominator Tree、Leak Suspects、GC Roots 路径4.1 Histogram先用正则把噪声折叠掉Histogram 按类名列出实例数和 Shallow Heap默认排序其实是按类名很多人第一眼看到一堆java.lang.String、char[]、int[]就以为找到了其实那只是 Java 程序的底色。正确的用法是先在顶部的正则框里做分组。比如怀疑是业务缓存问题com\.example\.(cache|buffer|holder)\..*几个要点点号必须转义成\.否则会匹配到任意字符分组用括号过滤后 Histogram 会把匹配到的所有类合并成一行显示实例总数比逐个类翻快得多。除了正则右键菜单里还有Group by package按包聚合和Group by class loader按类加载器聚合排查多应用部署时同一份类被加载多次特别好用。真正该盯的异常信号是某个业务类的实例数明显超过业务量级比如订单缓存说好 10 万条Histogram 里有 400 万或者某个基本类型数组的总 Shallow 远超其他所有类之和。4.2 Dominator TreeShallow 和 Retained 差在哪Dominator Tree 是整个工具里信息密度最高的一屏。它按支配关系组织对象父节点的 Retained Heap 包含所有被它支配的子节点。判断谁在吃内存只看这一列就够了。展开的姿势是从上往下逐层收窄顶层通常是一个线程或类加载器往下展开会看到Thread→ThreadLocalMap→ 你的缓存对象。每一层都看 Retained 占比占比低于 5% 的分支直接放弃不要在一棵小树枝上纠结。一个典型误判某个ArrayList的 Retained 有 1.8GB看着像泄漏展开发现里面装的是启动时一次性加载的字典数据跑多久都不变。这种情况不叫泄漏叫常驻内存大。区分方法很简单隔几小时抓两份 dump 对比同一个对象的 Retained 是否持续增长。稳定不变的是容量问题持续上涨的才是泄漏。4.3 Leak Suspects自动报告要带着怀疑读Leak Suspects 是 MAT 的自动推断打开 dump 后它会先跑一遍生成一页 HTML。结构一般是Problem Suspect 1/2/3每条给出一个或一组嫌疑对象、一句描述、以及一张从 GC Root 到该对象的路径图。读法上有讲究先看描述里的数字是怎么来的。常见的句式是One instance of X loaded by occupies N bytes这句话只说明 X 这个实例的 Retained 大没说是泄漏。如果 X 是框架里的连接池、缓存容器、类加载器元数据大概率是正常常驻。真正有价值的是它给出的路径图。点开Details看 accumulated objects 那一列能立刻看出这 N 个字节是被单个巨大对象撑起来的还是被几十万个小对象堆起来的。前者回去查是谁分配的用后面 4.4 的方法后者基本就是集合只进不出。自动报告省的是找路径的时间结论还得自己下。4.4 Path to GC Roots把弱引用和软引用剔掉定位到可疑对象后右键 →Path to GC Roots这一步是确认它为什么还没被回收。菜单里有好几个变体日常只用两个with all references全量路径可能很长很乱和exclude weak/soft references排除弱引用、软引用、虚引用。默认必须选后者。原因很实际WeakHashMap 的 key、ThreadLocal 的 Entry、各种缓存框架的软引用在图上都是可达的但 GC 真跑起来照样回收。用全量模式看路径很容易把一条软引用链误读成泄漏然后花半天去改一段根本没问题的代码。读路径的方向是从下往上最底下是你查的对象最上面是 GC Root。中途遇到的第一个业务代码特征的节点通常就是源头。最常见的两类根线程栈上的局部变量。线程池里的线程没退出栈帧一直挂着大对象——典型是某个任务把大 List 传进去后阻塞在 IO 上。静态字段。某个static Map只 put 不 remove随着请求量线性增长。5. 避坑与排查五条踩过的记录5.1 双击闪退日志里是 JNI 报错现象双击MemoryAnalyzer.exe窗口一闪就没了命令行启动刷一行Failed to load the JNI shared library。原因ini 里-vm指向的 JVM 位数和启动器不匹配。这个包的启动器是win32.win32.x86_64必须配 64 位 JDK指向 32 位 JDK或者-vm那行写成了-vm C:/...挤在一行启动器会把整行当成一个不存在的路径都会报这个。解决-vm单独一行下一行只放路径用javaw.exe的完整路径确认该 JDK 是 x64。改完先在命令行里带-consoleLog启动验证成功了再用图形界面。5.2 解析到一半报 Java heap space现象进度条走到 60%80% 停住弹An internal error occurred during: Parsing heap dump或者日志里一串OutOfMemoryError: Java heap space。原因ini 里的-Xmx还是默认的 1024m。建索引和算 retained 是内存消耗最大的两步几 GB 的 dump 根本不够。解决把-Xmx提到 dump 大小的 0.51.0 倍同时-Xms设为相同值确认机器物理内存留有余量MAT 之外系统还要用。改完必须完全退出 MAT 再启动ini 只在进程启动时读一次。5.3 新 dump 覆盖旧文件后结果全乱现象把新抓的 dump 覆盖到app.hprof打开后对象数和上次几乎一样点某些对象报地址越界retained 数值明显不合理。原因索引文件按文件名关联旧的.index、.o2ret.index还在MAT 认为索引有效就直接用了读到的是两份 dump 的混合结果。解决删掉dump.index、dump.o2hprof.index、dump.a2hprof.index、dump.o2ret.index、dump.a2ret.index、dump.threads全部重开。根治办法是 dump 文件名永远带时间戳app-20240115-0231-pid8823.hprof。5.4 dump 放网络盘加载几十分钟没动静现象进度条卡在 0% 或者极慢任务管理器里 CPU 不高、磁盘队列很长看起来像死机。原因MAT 对 dump 用的是 mmap 随机读网络共享尤其是跨机房的随机读延迟极高索引建不起来。解决拷到本地 SSD 再打开确认分区剩余空间大于 dump 大小的 1/3索引文件不小。顺带一提Windows 上 dump 文件被 MAT 打开期间是删不掉的分析完记得关掉窗口再清理。5.5 Leak Suspects 指向系统类加载器现象自动报告第一条是One instance ofcom.example.OrderCacheloaded bysystem class loaderoccupies 3.1GB。原因system class loader只是说明这个类由应用类加载器加载和泄漏没有任何因果关系。真正的判断依据是这个对象的 Retained 是否随时间增长。解决别急着改代码。隔 30 分钟再抓一份 dump用 Compare Basket 对比同一个类的实例数变化涨了才是泄漏不涨就是容量规划问题改缓存上限或者加内存都比改代码直接。6. 进阶用 OQL 把结论钉死别靠肉眼翻树图形界面点到第五层就开始靠猜了这时候我会切到 OQLObject Query Language直接把查询条件写出来。它是 SQL 的一个子集操作对象是堆里的实例。第一类查询找异常大的字符串定位拼接/缓存问题。-- 列出长度超过 8KB 的字符串看内容重复度 SELECT s.toString(), s.value.length AS len FROM java.lang.String s WHERE s.value.length 8192length是 MAT 给数组类型的内置属性s.value就是String底层的char[]或byte[]。结果表可以右键导出 CSV拿去和另一份 dump 的 CSV 按内容对齐重复的 key 一目了然。第二类查询按 retained 筛对象比在树上翻快得多。-- 找出 retained 超过 1MB 的集合实例 SELECT a, a.retainedHeapSize AS retained FROM java.util.ArrayList a WHERE a.retainedHeapSize 1048576retainedHeapSize依赖 retained 索引如果返回全是 0先在界面上打开一次 Dominator Tree 把索引建好再跑。同一个思路换成java.util.HashMap、com.example.*Cache都成立。还有一个我常用的组合先把可疑包名下的类全部查出来导出实例数和 retained再和上一次故障留下的 CSV 并排看。人来判断趋势不可靠数字并排放着涨没涨一眼就清楚。这套流程读过三次线上 dump 之后我基本不再靠 Histogram 一屏屏翻了——OQL 查出来的结果是可以贴进复盘文档、可以被别人复核的肉眼翻树翻出来的结论只能自己信。从那以后我每次拿到 dump都强制自己先跑一遍 OQL 把最大 retained 的前十个对象列出来存档再开始看图。这份 1.6.1 的 Windows 64 位构建就在工具箱里躺着命名规则一直没变按标题里的版本号找同名压缩包解压、改 ini、绑上 JDK 8 就能用。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑