尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Eclipse MAT 分析 hprof:从堆转储到内存泄漏定位
简介Eclipse Memory Analyzer ToolMAT堆内存分析工具包面向需要排查 Java 应用内存泄漏、OOM 与堆占用异常的开发者及运维人员也可作为理解 JVM 内存结构的辅助材料。压缩包共 5442 个文件、约 133.56MB以 html 帮助文档、png/gif 界面图示、jar 插件、xml 与 properties 配置为主并包含独立运行所需的可执行文件、批处理脚本及工作区目录解压后可按目录结构直接使用。目前已有 371 人学习。包内 MAT 主体、启动脚本与 hprof 快照样本可支持泄漏嫌疑分析、对象直方图与支配树查看、引用链追踪及多快照对比为性能优化和线上问题复盘提供可直接上手的排查环境。1. 拿到 2G 的 hprof 之后Eclipse MAT 把内存里的东西翻成可读的因果链一份 hprof 拷到本地用命令行工具打开能卡到怀疑人生好不容易进去了满屏类名也不知道该盯哪一个。Eclipse MATMemory Analyzer Tool就是专门处理这件事的它把二进制堆转储解析成对象图再用直方图、支配树和 GC Roots 引用链把“内存被谁占了”还原成一条能顺着读下去的因果关系。它不读业务日志也不分析 GC 日志只回答一个问题在快照那一刻堆里到底躺着什么对象哪些对象一旦被回收能连带释放多少空间。线上 OOM、堆内存缓慢上涨、缓存不释放、类加载器泄漏这几类问题用它最有效压测结束做一次内存检查也很好用。2. 从活着的进程取一份 hprof三条采集路径与参数取舍2.1 为什么只有堆转储能还原现场GC 日志能告诉你老年代从 2G 涨到 7G但告诉不了你是谁占的jmap -histo能看到某个类的实例数和浅堆总量却看不到对象之间的引用关系一个byte[]暴涨背后可能是一张 Map也可能是一批没关闭的流。堆转储不一样它保存的是对象图和类元数据的快照MAT 拿到这份图之后才能计算支配关系、才能顺着引用链往上走。所以排查内存问题堆转储不是“更详细一点的日志”它是唯一能重建现场的东西。理解这一点采集策略就清楚了要么在 OOM 那一刻自动落盘要么在问题复现的窗口期手动抓一份。前者考验的是 JVM 参数有没有提前埋好后者考验的是你能不能忍受一次全局停顿。2.2 jcmd、jmap 和 OOM 自动落盘怎么选先看进程还活着的情况。我一般先用jcmd确认目标进程和堆的实际情况再决定要不要抓# ① 动手前先确认进程身份避免 dump 错实例 jcmd 12345 VM.command_line jcmd 12345 GC.heap_info # ② jcmd 路线推荐JDK 7u40 之后自带 # 默认只 dump 可达对象等价于先做一次存活判断 jcmd 12345 GC.heap_dump /data/dump/app_1015.hprof # ③ 需要把不可达对象也留下来时加 -all # 好处是不会漏掉“已经没人引用但还没被回收”的现场 jcmd 12345 GC.heap_dump -all /data/dump/app_1015_all.hprof # ④ jmap 路线老环境或只有 jmap 时用 jmap -dump:formatb,file/data/dump/app_1015.hprof 12345这里有个很多人忽略的差别jcmd GC.heap_dump默认只写存活对象加-all才把不可达对象一并写进去而jmap -dump默认是全部对象加了live参数才会先触发一次 Full GC 再 dump。名字看着像行为正好相反。live这个参数是把双刃剑。它能让文件小一圈、让 MAT 里少一堆垃圾对象的干扰代价是触发一次 Full GC而这次 GC 很可能把你正想看的泄漏对象回收掉——尤其当你怀疑的是“本该被回收却因为某条引用链没被回收”的对象时live会把证据洗掉。所以我的习惯是怀疑泄漏就不加 live怀疑内存确实用超了就加 live。再说停顿。dump 是一个安全点操作整个 JVM 停下来等你把堆写完几个 G 的堆停几十秒很正常。线上执行前先把这个实例从负载均衡上摘掉或者挑低峰期别在业务高峰做这件事。文件大小大致等于堆里活对象的总量路径必须提前建好并且可写否则命令返回成功但文件根本没生成。2.3 OOM 现场最可靠的保底三个参数没人盯着的时候参数是唯一能救你的东西# 启动参数里至少要有这三行 -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/dump -XX:ExitOnOutOfMemoryError # 目录要提前建好并给足空间 mkdir -p /data/dump chmod 755 /data/dump df -h /data/dumpHeapDumpPath指向的目录必须已经存在JVM 不会帮你建目录不存在或者没权限时dump 会静默失败只在日志里留一行提示很容易被后来的重启日志冲掉。ExitOnOutOfMemoryError的作用是 dump 完成后立刻退出交给外部的重启策略接管避免进程在半死不活的状态下继续对外提供服务。容器环境还要多考虑两层。第一层是路径dump 一定要写到挂载出来的卷上写进容器自己的临时层容器一重启文件就没了。第二层更隐蔽——如果进程是被 cgroup 的内存限制直接杀掉的宿主机的 OOM killer 动手JVM 根本没有机会执行 dump 逻辑表现就是“明明配了参数却一个文件都没生成”。判断方法很简单看dmesg或者容器的退出码别一头扎进 MAT 里空等。3. 让 MAT 真的把文件打开索引、堆上限与无界面批处理3.1 第一次打开时MAT 在背后建什么hprof 是二进制格式MAT 打开它时要先做一次解析把类结构、对象偏移、引用关系转成自己的索引结构同时在 dump 文件旁边生成一批辅助文件。这个过程既吃内存又吃磁盘内存里要放解析结果磁盘上要写索引。所以“打不开”通常不是文件坏了而是 MAT 自己的堆不够。默认安装包里MemoryAnalyzer.ini给的堆上限偏保守处理一份 2G 的 dump 时经常直接抛Java heap space或者解析进度条走到 60% 就再也动不了。经验值是把 MAT 的-Xmx设成 dump 文件大小的 1.5 到 2 倍同时确认它跑在 64 位 JDK 上——32 位 JVM 再怎么调也上不去。还有一处容易忽略索引文件默认写在 hprof 同目录。如果这个目录是只读挂载很多生产环境的归档目录就是这样解析会在中途失败报错信息还相当含糊。稳妥的做法是先把 dump 拷到一个可写、空间充足的本地盘再打开别直接在网络挂载盘上开工。3.2 MemoryAnalyzer.ini 里该改哪几行配置文件里真正需要动的不多但位置要对# MemoryAnalyzer.ini -vm /opt/jdk-17/bin/java -vmargs -Xmx8g-vm必须写绝对路径指向bin/java这个可执行文件本身而不是目录写错了启动就是一个JVM terminated. Exit code13看不出任何有用信息。-Xmx8g放在-vmargs后面它控制的是 MAT 这个客户端进程跟被分析的应用没有关系。如果用比较新的 JDK 去跑老版本 MAT启动日志里可能出现InaccessibleObjectException这是模块化之后反射访问 JDK 内部结构被拦住了需要在-vmargs后面补上对应的--add-opens。遇到这类启动失败先看工作区目录下的.log文件通常在~/.eclipse或你指定的-data目录下比盯着闪退的窗口有用得多。3.3 不开界面也能出报告ParseHeapDump 批处理在生产跳板机上开图形界面往往不现实。MAT 提供了命令行解析入口一次生成三份 HTML 报告打包成 zipMAT_HOME/opt/mat DUMP/data/dump/app_1015.hprof # 如果 dump 是压缩过的先解开MAT 不能直接读 gz # gunzip -c app_1015.hprof.gz $DUMP $MAT_HOME/ParseHeapDump.sh $DUMP \ org.eclipse.mat.api:suspects \ org.eclipse.mat.api:overview \ org.eclipse.mat.api:top_components \ -vmargs -Xmx8g三个参数分别对应泄漏疑点报告、系统概览和顶层消费者报告输出是 dump 同目录下的几个 zip解开就是可以直接发给同事的 HTML。-vmargs -Xmx8g必须放在最后它透传给内部启动的 JVM写在报告参数前面会被当成报告名解析然后报一个莫名其妙的“未知报告”错误。我通常在拿到 dump 的第一时间就跑一遍这个批处理十几分钟内能判断这堆东西是“某个集合无限增长”还是“类加载器没释放”再决定要不要花时间开图形界面深挖。批处理同样需要磁盘空间和足够的内存挂到 CI 里时要记得给它单独的资源配额别和构建任务抢内存。4. 直方图、支配树、引用链把 retained size 读成一条因果链4.1 Shallow 和 Retained差的不是一点点这两个数字是最容易读错的地方指标含义典型误读Shallow Heap对象自身占用对象头加字段不含它引用的对象以为加起来就是内存总量忽略了数组元素和引用目标Retained Heap该对象被回收后能连带释放的总量按支配关系计算当成“独占内存”实际上共享引用的部分不算进去关键在支配关系如果对象 A 到 GC Roots 的所有路径都必须经过对象 B那 B 就支配 AA 的内存算在 B 的 retained 里。这意味着一棵树的根节点 retained 很大是正常的真正值得怀疑的是“本来不该这么大”的节点。举个常见的误判Histogram 里byte[]排第一占了 3G于是结论写成“字节数组泄漏”。实际点进支配树一看这些byte[]全挂在一张业务缓存 Map 的 value 上Map 又被某个静态字段持有。真凶是缓存没有淘汰策略byte[]只是受害者。4.2 从 Histogram 到 Dominator Tree 的固定动作我一般按这个顺序走很少跳步打开 Histogram先看实例数排前列的类重点找业务包名下的类而不是 JDK 自带的。选中可疑类右键Merge Shortest Paths to GC Roots并在弹出的选项里排除弱引用、软引用、虚引用——不然一堆WeakHashMap的内部结构会把路径淹掉。换到 Dominator Tree从根往下找第一个 retained 明显异常的节点看它的Retained Set里到底是什么。对可疑对象用Path to GC Roots确认持有它的是静态字段、线程局部变量还是某个线程栈上的局部变量。第 3 步和第 4 步的区别值得说清楚Dominator Tree 回答“谁占了大头”Path to GC Roots 回答“为什么它还没被回收”。前者定位规模后者定位成因两个都得看只看一个容易得出“这里内存多”这种没用的结论。要快速预筛jmap -histo也能顶上但它有代价# 采两次快照间隔几分钟比较实例数增量 jmap -histo 12345 /tmp/histo_1.txt sleep 300 jmap -histo 12345 /tmp/histo_2.txt # 看哪些类的实例数在涨 diff (awk NR2 {print $2, $3} /tmp/histo_1.txt | sort) \ (awk NR2 {print $2, $3} /tmp/histo_2.txt | sort)jmap -histo会遍历堆加:live还会触发一次 Full GC线上用要谨慎。它的价值在于成本低、能快速判断“是不是某个类的实例在持续增长”判断完再决定要不要抓完整 dump。4.3 读引用链找到那个本该断开却没断的点引用链是从对象往 GC Roots 走的一条路径读法是自下而上从可疑对象出发往上找第一个“业务代码还活着、但按设计不该再持有它”的节点。常见的几种形态很有辨识度链路过一个static final的 Map说明是全局缓存没做容量控制或过期淘汰。链路过java.lang.ThreadLocalMap说明线程池里的线程复用时没有清理上下文请求对象跟着线程活了下来。链路过某个ClassLoader说明热部署或动态加载的类没卸载连带整个加载器下的类元数据和静态字段都留着。链路过 JDK 内部的Inflater、ZipFile之类通常是对应的流没关。同样是“被静态字段持有”还要看这个字段所在的类是不是被容器管理的单例。框架自己的单例缓存里挂着一堆 Bean 是正常的别把这些当成泄漏——这也是 Leak Suspects 报告误报的主要来源。4.4 Leak Suspects 是线索不是判决书Leak Suspects 报告会直接给一句“问题疑似出在这里”配一张饼图和一段Accumulated Objects说明看上去很有说服力。但它走的是启发式规则对字符串常量池、类元数据、框架内部缓存这类“看起来很大但合理”的结构误报率不低。正确用法是把它当入口点开疑点详情里的See stacktrace看它是从哪条引用链推出来的然后回到 Dominator Tree 手动验证一遍。如果两边对不上以手动验证的为准。真正能写进故障报告的结论必须能说清楚“哪个类的哪个字段持有了多少对象为什么这些对象在这里不应该存在”光有一张饼图是不够的。5. 五个容易出错的场景从打不开到结论跑偏5.1 MAT 一打开就抛 Java heap space现象双击 hprof进度条走到一半弹出An internal error occurred during: Parsing heap dump或者干脆一个OutOfMemoryError: Java heap space界面卡死只能强杀。原因MAT 是普通 Java 程序解析结果放在自己的堆里默认MemoryAnalyzer.ini给的-Xmx往往只有 1G 上下面对几个 G 的 dump 根本不够用。还有一种情况是它悄悄跑在了 32 位 JDK 上上限被卡在 1.5G 左右怎么调都没用。解决改 ini 里的-Xmx按 dump 文件大小的 1.5 到 2 倍给同时用-vm明确指向 64 位 JDK 的bin/java。改完重启 MAT 再打开别在已经卡死的窗口里重试索引状态可能是脏的。5.2 解析到一半中断或者 dump 文件被锁住现象批处理跑了几分钟退出日志里只有一句Failed to parse heap dump同目录下多出一堆.index之类的半成品文件再打开报错说文件正在被占用。原因索引文件默认写在 hprof 同目录目录只读、空间不足、或者上一次解析崩溃留下了锁文件都会导致这一步失败。生产环境的归档盘经常是只读挂载很多人拷过去就直接打开。解决先把 dump 复制到本地可写盘再分析确认df -h有至少两倍 dump 大小的余量如果已经产生了半成品索引把同目录下的临时文件清掉重来。批处理脚本里最好显式检查退出码别让它失败之后还继续往下跑。5.3 jcmd 或 jmap 报 Unable to open socket file现象执行jmap -dump直接失败提示Unable to open socket file或者Attach Listener相关错误进程明明活着。原因attach 机制要求执行命令的用户和目标进程是同一个用户并且能看到同一个/tmp/hsperfdata_user目录。容器里更麻烦如果命令是在宿主机上执行、目标进程在容器自己的 PID 命名空间里attach 根本连不上。另外 JDK 和目标 JVM 版本差太多也可能出问题。解决进到容器内部、用启动应用的那个用户执行命令先跑jcmd pid VM.version验证通道是否通。实在不行再用jmap -F强制模式但它会跳过一致性检查拿到的 dump 可能不完整只能当兜底。5.4 用了 dump:live泄漏对象“自己消失了”现象明明监控上老年代一直涨抓下来的 dump 打开一看Histogram 里一切正常内存占用比线上看到的少一大截找不到任何可疑对象。原因jmap -dump:live会先触发一次 Full GC 再 dump那些“本该被回收、只是还没轮到”的对象在这一刻被清掉了dump 里剩下的都是真正的存活对象。如果问题本身就出在“对象该被回收却没被回收”这次 Full GC 恰好把证据抹平了。解决怀疑泄漏时用不带live的jmap -dump:formatb或者jcmd GC.heap_dump -all把不可达对象也留下来。另外抓 dump 的时机也很关键要在内存涨上去之后、重启之前抓重启后的第一份 dump 说明不了任何问题。5.5 把 retained size 当独占内存结论整体跑偏现象Dominator Tree 里某个节点 retained 显示 2.8G于是结论写成“这个对象独占 2.8G是泄漏源头”改完之后内存没降。原因retained size 是“回收它能释放多少”里面包含了它支配的所有对象而这些对象可能被别处共享引用回收之后未必真能释放那么多。还有一种情况是没排除弱引用和软引用把WeakHashMap里的缓存当成了强引用泄漏。解决看 retained 之前先在引用链分析里勾上排除弱引用/软引用然后对着Retained Set逐个确认这些对象是不是只被它一个人引用。真正要下结论得看“对象数量随时间的增长趋势”而不是某一时刻的绝对值。6. OQL 加两次 dump 对照把「疑似泄漏」变成一句可复现的结论图形界面点到后面容易迷失我最后都会落到两句可复现的话上一类对象有多少个两次快照之间涨了多少。前者靠 OQL后者靠两次 dump 对比。OQL 的入口在工具栏的 OQL 视图或者右键任意对象选Open Object Query Language Studio。语法接近 SQL但有两条硬规矩类名必须全限定字符串必须用双引号。-- 1) 先确认某个业务容器的实例数量级 SELECT * FROM INSTANCEOF com.example.cache.LocalCacheEntry -- 2) 只挑负载大的条目看 key 和 payload 的实际内容 SELECT l.key, l.payload FROM com.example.cache.LocalCacheEntry l WHERE l.payload.value.length 10000 -- 3) 粗筛元素数量异常大的 Map定位没有容量上限的容器 SELECT h FROM java.util.HashMap h WHERE h.size 5000第一句用的INSTANCEOF会把子类实例一并算进来比自己拼类名可靠。第二句里的payload.value.length依赖具体类型value在 JDK 9 之后是byte[]、之前是char[]写之前先确认运行环境。第三句是快速定位“谁在无限长大”的常用手法size是HashMap的实例字段可以直接访问。两次 dump 对照更直接分别打开前后两份 hprof在 Histogram 视图上选Compare to another Heap Dump把两份直方图放进 Compare Basket看差值列。真正值得追的是那些“实例数涨了几十万、但平均大小很小”的类——它们往往是集合条目或者包装对象以及“实例数只涨了几百、但每个都很大”的类那通常是缓存里塞进了大对象。从那以后我每次拿到 dump都强制自己先跑一遍ParseHeapDump.sh出三份报告再决定要不要开图形界面。这个习惯救过我好几次有一次界面里看着像字符串池的问题批处理报告里的顶层消费者直接指出了某个线程池的队列省掉大半天。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

网课题库接口ZERO拆解:鉴权限流、缓存与自建迁移

网课题库接口ZERO拆解:鉴权限流、缓存与自建迁移

网课题库接口这个东西,做过在线答题、刷题小程序、培训考试系统的人应该都不陌生。真正让人头疼的从来不是前端那几个页面,而是背后那几十万道题:题干、选项、答案、解析、章节归属、难度标签,还有各种题型混排。于是很多人一看到…

📅 2026/11/4 13:10:02
AI搜索时代官网可发现性工程:从抓取到引用的落地指南

AI搜索时代官网可发现性工程:从抓取到引用的落地指南

最近半年,我被问得最多的一个问题,从“官网还有没有必要做”变成了“为什么客户在AI助手里搜我们,出来的却是同行”。这个变化挺微妙的。过去几年,官网在不少团队眼里就是个“电子名片”——建好之后除了放个联系方式,…

📅 2026/11/4 13:10:02
Windows下Claude Code接入DeepSeek协议网关与排障

Windows下Claude Code接入DeepSeek协议网关与排障

很多人第一次在 Windows 上装 Claude Code,装完之后兴冲冲地敲命令,结果第一句话就卡住了:要么是终端报一堆看不懂的路径错误,要么是请求发出去之后一直转圈,要么干脆返回一个 401 或者 404。问题往往不在于 Claude Co…

📅 2026/11/4 13:10:02
MORE NEWS

更多资讯

📰

骑手金融防护系统:实时风控与场景化安防实践

1. 项目背景与核心价值 "护佑城市新动力 骑心共守金融权"这个项目名称乍看有些抽象,但拆解后能发现它精准捕捉了当下城市发展中的两个关键群体——新业态从业者(特别是外卖骑手、快递员等)和金融机构的协同关系。我在参与某城市数字…

📰

Folia主界面、播放面板与设置入口上手配置指南

第一次打开 Folia 的人,十个里有八个会在同一个地方卡住:界面确实干净,干净到你不知道下一步该点哪。左上角有一排图标,底部有一条灰黑色的横条,右上角有个小齿轮静静待着,主界面中间大片留白——这时候大多…

📰

Agent记忆组件:分层架构与实战选型指南

1. 什么是Agent记忆组件:不是“记住东西”,而是让AI拥有时间感和身份感你刚写完一个能调用天气API、查完航班、再订好酒店的Agent,运行三次后发现——它完全不记得上一次帮你订的是哪家酒店,也不记得你说过“讨厌早班机”&#xf…

📰

金蝶云星空V7.3返利管理:全渠道结算链路与配置避坑指南

简介:这份PPT面向电商与分销领域的业务人员、实施顾问及企业管理者,系统讲解金蝶云星空V7.3版本中返利管理的整体方案与落地路径,帮助解决返利政策制定粗放、流程执行不清、结算方式单一等实际问题。资源包共1个文件,为pptx演示文…

📰

436页咨询级PPT模板:结构型版式设计与汇报逻辑实战指南

简介:这套罗兰贝格PPT模板面向商务人士、咨询顾问与需要高频制作演示文稿的专业人群,以模块化版式解决从公司介绍、市场分析到项目汇报的排版效率问题,属于可直接套用的进阶素材。压缩包内共1个PPT文件,体积约4.45MB,内…

📰

线性回归原理与应用:从基础到工程实践

1. 线性回归的本质与核心价值 线性回归是机器学习领域最基础也最重要的算法之一,它就像数学中的加减法一样,构成了整个预测模型的基石。我第一次接触线性回归是在研究生时期的计量经济学课上,当时教授用"用尺子量身高"的比喻让我瞬…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬