简介:这是一款面向IBM J9虚拟机开发者的内存分析工具资源,专为排查Java应用内存泄漏、对象过度分配与内存碎片等问题而准备。资源包共3个文件,包含可执行jar主程序、xml配置与bat启动脚本,压缩包约5.45MB,解压后即可在本地运行,无需复杂部署。工具提供内存泄漏检测、对象生命周期分析、内存碎片识别、对象引用关系图及类统计等可视化视图,帮助开发者从heapdump快照中直观定位占用内存较大的对象与类,进而优化代码与内存管理策略。内容还涉及heapdump生成方式、Java内存模型、垃圾回收原理、强弱软虚引用区别及JVM调优等知识点,适合具备一定Java基础、需要诊断线上内存问题的中高级开发者参考。目前已有612人学习下载,可作为日常内存分析与性能调优的实用辅助工具。
1. IBM Java 堆内存分析工具 HeapAnalyzer:从 dump 文件到泄漏根因
线上 IBM JDK 跑着的交易系统,凌晨两点突然 Full GC 频繁,堆从 2G 涨到 6G 不回落,重启能撑两天又复发。这时候日志里能看到的只有OutOfMemoryError,真正的问题藏在堆转储文件里。IBM Java 堆内存分析工具 HeapAnalyzer 就是干这件事的:读 IBM JVM 生成的 heapdump(PHD 格式),把对象、类、引用链、支配树摊开给你看,定位是谁在持续持有对象不放。它和 MAT 最大的区别在于——MAT 读不了 IBM PHD 格式,而 IBM 自家这套工具能直接吃。适合谁?维护 WebSphere、跑 IBM JDK 的 Java 后端、被内存泄漏折磨过的运维和开发。下面按「怎么拿到 dump → 怎么用工具读 → 怎么定位泄漏 → 怎么避坑」走一遍。
2. 先搞清楚 IBM JVM 的堆转储长什么样
2.1 PHD 格式和 HotSpot 的 hprof 不是一回事
很多人第一次翻车就翻在这:拿 HeapAnalyzer 去开一个 HotSpot 生成的.hprof,工具直接报格式错误。IBM JVM(现在叫 Semeru,老版本叫 J9)默认生成的堆转储是PHD(Portable Heap Dump)格式,文件头是portable heap dump字样,扩展名常见.phd。HotSpot 的.hprof是另一套二进制结构,两者不通用。所以第一步不是打开工具,而是确认你手上的 dump 是哪来的。
判断方法很简单,用file命令或者看文件头:
# 看文件头前 64 字节,PHD 会带 portable heap dump 字样 head -c 64 heapdump.20240115.021033.1234.phd | strings # 或者直接 file file heapdump.20240115.021033.1234.phd如果输出里出现portable heap dump,那就是 IBM 的 PHD,HeapAnalyzer 能读。如果出现JAVA PROFILE,那是 hprof,得换 MAT 或 JProfiler。这一步花十秒,能省掉后面半小时的困惑。
2.2 触发一次可控的堆转储
别等 OOM 自己生成,那样往往发生在最不该发生的时刻。IBM JVM 支持主动触发,常见做法有两种。
第一种,用kill -3发信号给进程,JVM 会在日志目录生成 heapdump 和 javacore:
# 找到 java 进程 pid ps -ef | grep java | grep -v grep # 发 QUIT 信号,触发 heapdump + javacore kill -3 <pid>第二种,用jcmd(IBM JDK 8 以后也支持):
# 列出可用的诊断命令 jcmd <pid> help # 直接生成 heapdump,指定路径 jcmd <pid> GC.heap_dump /data/dump/heap_$(date +%Y%m%d%H%M).phd参数说明:GC.heap_dump后面的路径要确保磁盘有足够空间,PHD 文件大小通常接近堆的活跃部分,6G 堆可能生成 2~4G 文件。生产环境务必先df -h看一眼,磁盘写满会引发更严重的故障。另外,生成 dump 会 STW(Stop-The-World),几秒到几十秒不等,低峰期操作。
提示:如果 JVM 启动参数里加了
-Xdump:heap:defaults相关配置,OOM 时会自动落盘。查一下-Xdump的配置,避免 dump 文件把磁盘撑爆。
2.3 拿到 dump 之后先别急着开图形界面
大文件(超过 2G)直接拖进 HeapAnalyzer 图形界面,很可能卡死或者 OOM——工具本身也是 Java 写的,默认堆可能不够。稳妥做法是先给工具自己加内存:
# 启动 HeapAnalyzer 时指定更大的堆,比如 4G java -Xmx4g -jar ha456.jarha456.jar是常见版本命名,具体文件名以你拿到的工具包为准。-Xmx4g这个参数要根据 dump 文件大小调,经验值是 dump 文件的 1.5~2 倍。这一步不做,后面所有分析都是空谈。
3. HeapAnalyzer 读 dump 的四个核心视图
3.1 用对象数量排序找「异常膨胀的类」
打开 dump 后,第一眼看的不是引用链,而是类直方图(Class Histogram)。HeapAnalyzer 里对应的是按对象数量或占用内存排序的视图。正常业务系统里,java.lang.String、char[]、HashMap$Node排前面很正常。但如果某个业务类,比如com.xxx.OrderContext,对象数达到几十万甚至上百万,那基本就是嫌疑对象。
操作路径:菜单里找Analysis→Class Histogram,或者直接看主界面的对象统计表。重点看两列:Instance Count(实例数)和Total Size(总占用)。一个类实例数远超业务量级,比如订单系统里OrderContext有 50 万个,而当前在线订单才几千,这就是信号。
这里有个容易忽略的点:IBM JVM 的 PHD 里,对象是按类聚合的,但数组类型(比如[Ljava.lang.Object;)会单独列出来。如果看到某个Object[]数量巨大,往往意味着某个集合类在疯狂扩容,得顺着引用链往上找。
3.2 支配树(Dominator Tree)才是定位泄漏的主战场
类直方图只能告诉你「谁多」,不能告诉你「谁在持有」。真正定位泄漏靠的是支配树。支配树的定义是:如果从 GC Roots 到对象 B 的所有路径都经过对象 A,那 A 支配 B。换句话说,释放 A 就能释放 B。HeapAnalyzer 里可以生成支配树视图,按 retained size(保留大小)排序。
操作上,找到支配树视图后,按Retained Size降序排。排第一的往往是一个巨大的HashMap或者ArrayList,点开看它持有什么。常见场景:一个静态的ConcurrentHashMap做本地缓存,没有淘汰策略,越积越多。支配树会直接把这个 map 标出来,retained size 可能就是几个 G。
参数上注意:HeapAnalyzer 计算支配树比较吃内存,大 dump 可能要跑几分钟。如果工具卡住,先确认-Xmx给够了。另外,支配树里的 retained size 是估算值,和实际 GC 后的释放量会有偏差,但用来排序找嫌疑对象足够。
3.3 引用链(Reference Chain)追到 GC Roots
找到嫌疑对象后,下一步是看谁在引用它。HeapAnalyzer 里右键对象,选References或者Path to GC Roots。这一步的目的是排除弱引用、软引用这些「假引用」,找到强引用路径。
典型输出会是一条链,比如:
Thread -> ThreadLocalMap -> Entry -> value -> OrderCache -> HashMap$Node -> OrderContext看到ThreadLocalMap就要警惕了。线程池场景下,ThreadLocal 如果没 remove,value 会一直挂在线程上,线程不销毁,对象就不释放。这是 Java 后端最经典的泄漏之一,HeapAnalyzer 的引用链能直接把它揪出来。
操作细节:引用链视图里,注意区分strong、weak、soft、phantom。只有 strong 引用才会阻止 GC。如果一条链中间有 weak,那这个对象其实可以被回收,不是泄漏源。HeapAnalyzer 会用不同标记区分,看的时候别被长链吓到,先看引用类型。
3.4 对比两次 dump 找「只增不减」的对象
单次 dump 只能看快照,判断不了趋势。真正稳的做法是间隔一段时间抓两次 dump,做 diff。HeapAnalyzer 支持打开两个 dump 文件做对比,或者用命令行工具jmit辅助。
操作步骤:第一次 dump 在业务低峰抓,等半小时或触发一次 Full GC 后再抓第二次。对比两次的类直方图,看哪些类的实例数只增不减。比如第一次OrderContext有 10 万,第二次 15 万,中间没有对应业务量增长,那就是泄漏。
# 假设有两次 dump,用命令行做简单对比(具体工具名以实际为准) # 这里演示思路:分别导出类统计,再 diff jmit -dump heap1.phd -histo > histo1.txt jmit -dump heap2.phd -histo > histo2.txt diff histo1.txt histo2.txt | grep '^>'参数说明:-histo导出类直方图,diff找新增行。实际用 HeapAnalyzer 图形界面做对比更直观,但命令行适合自动化,比如定时任务里跑。注意两次 dump 之间最好触发一次 Full GC,排除临时对象干扰。
4. 避坑:HeapAnalyzer 实操中的五个血泪教训
4.1 现象:工具打开 dump 报「Unsupported dump format」
原因:拿 HotSpot 的 hprof 喂给了 HeapAnalyzer,或者 PHD 文件在传输过程中被截断。PHD 文件很大,用scp或ftp传输时如果没校验,很容易损坏。
解决:先file确认格式,再md5sum对比源文件和目标文件。如果是 hprof,换 MAT。如果是 PHD 损坏,重新抓一次,传输时用rsync -c做校验。
4.2 现象:HeapAnalyzer 界面卡死,进度条不动
原因:工具自身堆内存不够,或者 dump 文件超过 4G,32 位 JVM 根本读不了。
解决:确认用 64 位 JDK 启动工具,-Xmx给到 dump 文件的 2 倍。如果还是卡,先用命令行工具jmit做预处理,导出精简后的数据再进图形界面。
4.3 现象:支配树里 retained size 最大的对象,代码里根本找不到
原因:可能是框架内部的对象,比如 Spring 的DefaultListableBeanFactory或者 MyBatis 的Configuration,它们持有大量反射元数据。这些不是泄漏,是正常开销。
解决:看引用链,如果链的末端是框架的静态字段,且对象数量稳定,可以排除。重点找业务类且数量持续增长的对象。别一上来就怀疑框架。
4.4 现象:引用链显示ThreadLocalMap,但代码里明明 remove 了
原因:remove 调用在 finally 里,但线程池的线程被复用了,remove 之前线程可能已经被中断,或者 remove 的 key 和 set 的 key 不是同一个对象(比如用了不同的 ThreadLocal 实例)。
解决:检查 ThreadLocal 是否用static final修饰,确保全局唯一。另外,线程池场景下,如果任务提交后线程被复用,ThreadLocal 的清理要放在任务执行的最外层 finally。HeapAnalyzer 里可以看ThreadLocalMap的 Entry key 是不是 null,null key 说明 ThreadLocal 实例已被回收但 value 还在,这是典型泄漏。
4.5 现象:两次 dump 对比,对象数没涨,但内存就是下不去
原因:可能是内存碎片或者大对象直接进老年代,对象数不多但单个对象巨大。比如一个byte[]占了 500M。
解决:在类直方图里按Total Size排序,而不是按实例数。看有没有单个byte[]或char[]异常大。另外,IBM JVM 的-Xgcpolicy参数会影响碎片,gencon和balanced表现不同,必要时调整 GC 策略。
5. 进阶:用脚本把 HeapAnalyzer 的结论固化成巡检项
HeapAnalyzer 图形界面适合排查,但不适合日常巡检。真正省事的做法是把 dump 分析脚本化,定期跑,发现异常就告警。思路是:用 IBM JDK 自带的jmit或者 HeapAnalyzer 的命令行模式,导出类直方图,再用脚本判断阈值。
#!/bin/bash # 定期抓 dump 并分析,超过阈值告警 PID=$(ps -ef | grep java | grep -v grep | awk '{print $2}' | head -1) DUMP_DIR=/data/dump TS=$(date +%Y%m%d%H%M) # 抓 dump jcmd $PID GC.heap_dump $DUMP_DIR/heap_$TS.phd # 导出类直方图(工具名以实际为准,这里示意) jmit -dump $DUMP_DIR/heap_$TS.phd -histo > $DUMP_DIR/histo_$TS.txt # 检查业务类实例数,比如 OrderContext 超过 20 万就告警 COUNT=$(grep 'com.xxx.OrderContext' $DUMP_DIR/histo_$TS.txt | awk '{print $2}') if [ "$COUNT" -gt 200000 ]; then echo "ALERT: OrderContext instance count = $COUNT" | mail -s "Heap Alert" ops@example.com fi # 清理旧 dump,保留最近 3 天 find $DUMP_DIR -name 'heap_*.phd' -mtime +3 -delete逻辑说明:脚本先抓 dump,再导出直方图,然后 grep 目标业务类,取实例数做阈值判断。awk '{print $2}'假设直方图输出第二列是实例数,实际列序要看jmit的输出格式,可能需要调整。告警用mail或对接监控系统。清理旧文件防止磁盘满。
参数上,阈值 20 万要根据业务量定,别照抄。建议先跑一周只记录不告警,观察正常波动范围,再设阈值。另外,抓 dump 有 STW,巡检频率别太高,一天一次低峰期足够。
注意:脚本里的
jmit和jcmd路径要写全,cron 环境变量和登录 shell 不一样,容易找不到命令。用which jmit确认路径,或者直接在脚本里export PATH。
这套巡检跑起来之后,大部分内存泄漏能在爆发前发现。我自己的习惯是:每次上线新版本后,连续三天每天抓一次 dump 对比,确认没有新增的持续增长对象。这个习惯帮我拦下过好几次「本地缓存没设上限」的低级错误。HeapAnalyzer 不是万能的,但配合定期 dump 和 diff,它能把内存问题从「玄学」变成「可观测」。希望帮到你。
本文还有配套的精品资源,点击获取