☰
HeapAnalyzer实战:从heap dump到内存泄漏根因定位
2026/10/14 12:58:20 网站建设 项目流程

简介:在Java服务运维中,内存溢出(OOM)和内存泄漏是最棘手的稳定性问题之一。当堆内存持续攀升,heap dump文件成为定位根因的关键证据。HeapAnalyzer作为一款轻量级的堆转储分析工具,通过解析对象引用关系,将海量对象组织成可读的对象树,帮助开发者快速识别Retained Size异常的节点,并沿引用链追溯至GC Root。本文从对象树的解读、shallow size与retained size的区别、两次转储对比等基础概念出发,结合实际案例展示如何利用该工具定位全局缓存、ThreadLocal脏数据及类加载器泄漏等典型场景。无论是后端开发还是中间件运维,掌握堆转储分析方法,能显著提升内存问题的排查效率,为线上稳定性提供保障。

1. 线上服务又 OOM 了:heap dump 拿到手,HeapAnalyzer 到底能帮你做什么

凌晨两点,告警群里弹出一条“内存使用率持续超过 85%”的消息,值班同事重启了一下实例,半小时后曲线又抬头。等你登录服务器时,jmap刚导出的那份 heap dump 文件已经躺在磁盘上——好几个 GB,比线上很多数据库都大。这个时候,你需要的是一个能离线分析堆转储、把“谁占着内存不放”讲清楚的工具。HeapAnalyzer(常被简称为 HA)就是这类工具里很有代表性的一个:它把 heap dump 里的对象、引用关系、保留内存算成一张可展开的对象树,让你从“文件打不开、数据看不懂”走到“有一个 HashMap 占了 60% 的堆,引用链指向某个全局缓存”。这篇文章面向被 OOM、内存泄漏、GC 频繁折腾过的后端开发和中间件运维,我会按自己实际用下来的路径,把环境准备、转储获取、对象树判读、两次转储对比以及踩过的坑讲透。

2. 跑通 HeapAnalyzer 的第一趟:环境、启动参数与堆转储获取

2.1 解压就能用的运行方式与它自己的内存参数(-Xmx 怎么给)

HeapAnalyzer 是一个基于桌面界面的 Java 程序,常见的发行形态是一个压缩包,解压后在bin目录下能找到启动脚本(Windows 下是.bat,Unix/Linux 下是.sh)。它不像有些分析工具需要装服务端、连数据库,属于“拿到包、有 JDK、双击脚本就能起”的轻量路线。注意,它依赖本机已安装的 Java 运行时,启动脚本本质上是找到java命令再去加载主程序,所以机器上最好有 JDK 8 或更新的版本。

但这里有一个很多人第一次用会翻车的点:HA 本身也是一个 Java 进程,分析大堆转储时,它自己要消耗大量内存。如果你不管它,默认启动脚本给的堆可能只有几百 MB,碰上 2GB 以上的 dump 文件,轻则分析到一半卡死,重则直接闪退。我一般会在启动脚本或者命令行里显式指定-Xmx,并且把参数和转储文件大小做个粗略挂钩:

heap dump 文件大小建议给 HA 的 -Xmx分析机内存底线说明
2GB 以下1g ~ 2g8GB笔记本也能跑,加载速度快
4GB ~ 8GB4g ~ 6g16GB这是最常见的线上转储规模,耐心等分析进度
16GB 以上8g 或更高32GB 以上建议在专门的分析机上跑,别跟业务服务抢内存
# 常见做法:直接用 java 命令指定堆大小启动 java -Xmx6g -jar HeapAnalyzer.jar # 如果你是用解压目录里的 runHA.sh 启动,可以先修改脚本里的 JVM_ARGS

这段命令里的-Xmx6g指的是 HA 自身 JVM 的最大堆内存,不是被分析转储的堆大小。逻辑很简单:HA 要把 dump 里的对象图加载进自己的堆里建索引,转储越大,它需要的空间越多。经验值上,-Xmx至少取转储文件大小的 1.5 到 2 倍比较稳,但也要考虑分析机物理内存,别把机器本身搞到开始 swap。

参数怎么改取决于你的场景:如果只想快速确认某个类是不是内存大头,可以给少一点内存,分析速度反而快;如果要做全量对象树的引用追踪,就按上表给足。还有一个小习惯:分析超大转储时,把系统里没用的程序关掉,特别是其他 Java 进程,避免系统内存吃紧。

2.2 从 JVM 拿堆转储:IBM 系 JDK 的 Xdump 配置与手动触发

HeapAnalyzer 最顺手的场景是 IBM JVM(包括 J9 系的虚拟机)产出的转储文件。这类 JVM 的堆转储触发方式不是大家更熟悉的jmap -dump,而是通过启动参数-Xdump来配置。最常见的做法是在 JVM 启动参数里固定写一个 heap dump 配置,指定文件路径和触发事件。

# 在 JVM 启动参数里加上这行,按需修改路径 -Xdump:heap:file=/data/dump/heapdump.phd:events=vmstart,oom,user

解释一下这段配置:file=指定转储文件输出路径,文件后缀.phd是 IBM JVM 的转储格式,HeapAnalyzer 原生支持这种格式;events=指定触发时机,vmstart表示 JVM 启动时立即转储一次,oom表示抛出 OutOfMemoryError 时转储,user表示允许用户通过信号或工具手动触发。如果只要排查线上 OOM,建议只留events=oom,这样不会因为频繁转储把磁盘打爆,但初次验证配置是否生效时,加一个user会很方便。

运行时手动触发一般用kill -3 <pid>。有人会惊讶,为什么不是kill -9?kill -3在 IBM JVM 上会触发一次线程转储和可配置的 dump 操作,前提是启动参数里已经配好了events=user对应的-Xdump:heap项。如果没配,kill -3通常只会打线程栈到 stderr,不会生成堆转储文件,这也是个常见误区。

还有一个实际我会用到的点:如果应用跑在标准 Oracle/OpenJDK 上,也能用jmap -dump:format=b,file=app.hprof <pid>拿到 dump,但 HeapAnalyzer 对标准 hprof 格式的兼容性不如 phd,偶尔能打开,偶尔分析结果不完整。这种场景下我更倾向于用 MAT 之类的工具,或者干脆确认生产环境确实没有 IBM JVM 可换之后再做决定。总之,用 HA 前先确认手里的 dump 是什么格式,能少走一大段弯路。

2.3 打开第一个转储文件:菜单路径、等待过程与界面布局

启动 HA 之后,主界面就是一个很典型的桌面工具窗口,顶部是菜单栏,中间是一块待分析的空白区域。打开文件的操作不复杂:菜单栏里找到File->Open,选中你的.phd文件,确认后工具会开始加载和分析。如果文件很大,你会先看到一个进度条,同时机器 CPU 会明显升高,这是 HA 在建立对象索引、计算对象大小和引用关系,属于正常现象。

第一次打开 4GB 左右的转储文件,我通常会给它 3 到 5 分钟时间。这个过程中不要去频繁点击界面,也不要开着好几个大型应用,否则分析线程容易被系统调度拖慢。分析完成后,界面上会出现一棵树形结构,常见布局是:左侧是类的层级/对象树,你可以按包名或类名展开;右侧或下方是选中节点的细节信息,包括实例数、Shallow Size、Retained Size 和引用关系。有些版本还会提供过滤输入框,可以在树上方直接输入类名关键字,快速过滤出你关心的节点。

这里有个新手常问的问题:分析完成后为什么看不到“报告”或“结论”?因为 HA 的定位是交互式对象树分析工具,它把对象关系摆在你面前,结论需要你自己判断。所以使用它的正确姿势不是等结论,而是带着“谁占得多、谁活得久、谁引用谁”的问题去树上找答案。后续章节我会讲怎么读这棵树,以及怎么判断一个对象到底是不是泄漏源头。

3. 读懂对象树里的引用链:把可疑对象一步步定位到代码

3.1 对象树与保留集:shallow size、retained size 和支配链

HeapAnalyzer 最核心的东西就是对象树。这棵树不是按类继承关系组织的,而是按 JVM 堆里对象的引用关系组织的:谁被谁引用,谁引用了谁,层层展开。要读懂它,得先分清两个概念:Shallow Size是对象自身占用的内存,不包括它引用的其他对象;Retained Size是“如果把当前对象及其引用链上的对象全部回收,能释放多少堆内存”。

打个比方,一个 HashMap 实例的 Shallow Size 可能只有几十字节,但它背后可能挂着上万个 Entry、字符串和业务对象,Retained Size 可能是几百 MB。所以分析内存问题,重点看 Retained Size,别被 Shallow Size 骗了。HA 的对象树里,每个节点通常会显示这两个数值,你要找的是 Retained Size 异常的节点——而不是单纯“看起来很大的对象”。

与 Retained Size 紧密相关的是支配链(dominator chain)思想:在引用树里,如果对象 A 是对象 B 的支配者,意味着 B 要存活,A 必须存活;A 一旦被回收,B 也逃不掉。沿支配链往上走,你会看到谁在“撑着”这堆对象不释放。实际操作中,我拿到一棵对象树后,第一步不是展开细节,而是先看顶层节点的 Retained Size 分布,找到那个占了总堆 30% 以上的子树,再往下钻。

还有一个有价值的字段是实例数。如果一个类在堆里有几十万个实例,每个都不大,但加起来很可观,这通常是“批量创建后没释放”的信号。反之,如果实例数不多但 Retained Size 巨大,多半是某个大集合或大缓冲被长期持有。两条线索指向的代码位置是完全不同的,前者去查循环里 new 对象的地方,后者去查缓存、静态变量和线程局部存储。

3.2 按内存占用排序:先看大头,再找异常的聚集节点

对象树展开后节点非常多,直接顺着树往下点会迷路。我一般的做法是先把视图切到按Retained Heap或Total Heap Size排序,让大头排到最上面。排序操作通常在树顶部的表头点击即可实现,有些版本则通过右键菜单里的排序选项。目的只有一个:让占用最高的类/对象先暴露出来,缩小排查范围。

排序之后,你会看到一个很典型的分布:最上面往往是byte[]、char[]、java.lang.String、java.util.HashMap$Node[]之类的容器和数组。别急着下结论说“byte[] 占内存最多所以是根源”,要往下展开看是谁引用了这些数组。比如byte[]的 Retained Size 排第一,但它的引用方可能是 HTTP 响应缓冲、日志框架的环形缓冲、或者是某个全局缓存的值对象——这几种情况对应的修复代码完全不一样。

这时过滤功能就派上用场了。在 HA 的过滤框里输入包名或类名关键字,比如com.yourcompany.cache,可以快速把对象树收缩到你关心的范围,避免被byte[]和String淹没。我习惯的过滤顺序是:先不过滤看全局 Top 20,再按公司自有包名过滤,最后针对可疑类单独展开。这样做的好处是:全局视角能发现一些你没想到的“隐藏大户”,自有包名过滤则能快速定位业务代码是否参与其中。

展开一个可疑节点时,重点看它的子节点里是否有大量同类业务对象。举个例子,如果com.example.OrderService的某静态字段持有一个ArrayList,实例数只有 1,但 Retained Size 巨大,且展开后能看到百万级的Order对象,这就是典型的集合类长期持有业务数据。另一种情况是节点自身实例数几十万,每个 Retained Size 很小,但它们全都挂在同一个父节点下,父节点的 Retained Size 因此被撑大。

3.3 沿引用链追根:GC Root、类加载器和缓存的判断思路

找到可疑节点之后,下一步是回答“谁还活着,所以它也不能死”。在 HA 里选中节点,通常可以通过菜单或右键找到查看引用路径的操作,比如Find References、References之类。它会给你一条从 GC Root 到当前对象的引用链,GC Root 常见类型包括:当前活跃线程的栈帧、静态变量、JNI 引用、系统类加载器等。

分析引用链时,我会先看根的类型。如果引用链是Thread -> ThreadLocal -> ThreadLocalMap -> Entry -> value -> 业务对象,基本可以断定是线程局部变量没清理,典型场景是线程池里的线程复用时,ThreadLocal没有被remove。如果根是System Class或某个类加载器,且挂着一个静态集合,那就奔着全局缓存和单例去了。

类加载器泄漏是我见了比较多次的情况:一个类加载器明明已经不需要了,但因为某个静态变量还引用着它加载出来的类,导致整个类加载器下面所有对象都回收不了。在对象树里,你能看到ClassLoader节点下挂着大量已废弃的类实例,每次应用热部署或动态加载 jar 包后,heap 里就多一份这样的残留,反复几次,内存就顶不住了。判断它是不是泄漏,一个实用指标是:两个时间点对比,ClassLoader节点数量和它下面挂的对象实例数是否持续净增长。

对于缓存类引用链,判断要点是“有没有上限”。像 Guava Cache 或 Caffeine 这种带容量限制和过期策略的缓存,在对象树里会体现为某个稳定的 Retained Size 水位;而简单的static Map如果只写不淘汰,则实例数和 Retained Size 会一路涨到 OOM。前者是配置问题,后者是代码问题,两者在 HA 里的区别很明显:水位线 vs 增长线。

4. 用两次堆转储做趋势对比:内存泄漏是这样被实锤的

4.1 两个时间点的转储怎么采:低峰、高峰与 OOM 前后

单看一次 heap dump,最容易犯的错误是把“本来就是这么大的缓存”误判成“泄漏”。缓存、连接池、线程池在运行平稳后都有一个合理的内存占用峰值,单次 dump 只能告诉你“现在这个样子”,没法告诉你“这是不是异常”。所以我的习惯是:要定性泄漏,至少采两次 dump,最好在三个时间点各采一次。

时间点选择有讲究。第一次选在业务低峰、JVM 刚完成一次 Full GC 之后,这时的堆应当处于相对干净的状态;第二次选在业务高峰或者 OOM 即将发生的临界点;如果服务已经反复 OOM,OOM 触发时自动生成的 dump 本身就可以作为第二次样本。IBM JVM 上采集两次 max dump 的启动参数可以这样配:

-Xdump:heap:file=/data/dump/heapdump_%Y%m%d_%H%M%S.phd:events=oom

%Y%m%d_%H%M%S是时间戳占位符,这样的好处是每次 dump 文件名都不同,不会相互覆盖。线上环境你还可以考虑在events=oom,user里保留user,这样你随时可以用kill -3在业务高峰手动按一下快门,拿到一个“正在进行时”的现场。

需要注意:两次 dump 之间不要重启应用,否则对象树、类加载器、线程状态全变了,对比就没有意义。有一次某开发者为了“快速缓解问题”重启了服务,然后拿着重启前后的两个 dump 分析,结果当然是什么都对比不出来,因为旧对象全被清掉了。要对比的是同一个进程生命周期内的不同阶段,不是两个不同进程。

4.2 对比维度与判断阈值:实例数、retained size 与增长曲线

拿到两个时间点的 dump 后,对比方式可以很朴素:在 HA 里分别打开两次分析,或者用两个 HA 实例并排窗口,对同一个类比较三个数字——实例数、Shallow Size、Retained Size。这比在一个 dump 里翻引用链更快地给出全局判断。

我常用的对比表长这样:

对比项低峰 dump高峰 dump判定倾向
byte[]实例数12 万310 万明显净增长,疑似缓冲堆积
Session对象800800无变化,排除
WorkThread实例3232无变化,排除
某缓存 Map 的 Retained Size1.2GB1.3GB小幅波动,可能是水位线
某业务实体对象2 万98 万强增长,重点怀疑

判断阈值上,我个人的经验是:某类对象的 Retained Size 在两次采样间增长超过 50%,且实例数同步增长、没有回落迹象,就值得沿着引用链追下去;如果实例数翻倍甚至更多,基本可以直接定位到代码里的集合写入路径。反过来,如果实例数几乎不变、Retained Size 有波动,更可能是某些对象的内部数组扩容或 GC 延迟,不必急着动刀。

还有一种情况:两次 dump 的类实例数都很多,但一个是低峰也有海量缓存,一个是高峰才暴涨。前者是“设计如此但配置过大”,后者才是“代码级泄漏”。对比的意义就在这里——它逼着你去解释“为什么这个类的数量变了”,解释不了的那一项,就是问题所在。

4.3 验证手法:把分析结论带回线上

分析结论不能只停在“HA 树里看到这个类很大”这一步,还要回到线上验证,否则可能是采样时机导致的假象。我通常会带着可疑类的全限定名回服务器做两件事:第一,看当前 JVM 里这个类的实例总数是否还在增长;第二,查代码里这个类的创建/挂载路径,确认是不是只有一条持有链。

标准 JDK 环境里,jmap -histo:live <pid> | grep 类名可以快速拿到实例数和占用字节数;连续执行几次,间隔几分钟,如果实例数一直往上走,就证明不是采样瞬时值。IBM JVM 环境没有完全等价的命令,但可以通过再次手动触发 dump 来分析确认。验证还有一种做法:仔细看引用链末端的业务对象是在哪个模块里 new 出来的,然后对照代码,看它是否应该在某个业务完成后被置空——如果逻辑上没有释放路径,那就是根因。

这里有个容易被忽略的细节:验证时不要只看对象数量,要看 Retained Size 的变化也要一起看。比如有个临时对象被频繁创建,但每次都能被 GC 回收,那它的实例数在高频采样里可能也不低,但 GC 后归零。真正的泄漏是“GC 也收不掉”,因为总有活着的引用指向它。所以最硬的验证是:线上触发一次 Full GC 后立刻采样,如果类实例数纹丝不动,说明对象已经被某个全局引用钉死了,实锤泄漏。

5. HeapAnalyzer 避坑记录:5 个常见坑和对应排错办法

5.1 现象:打开大转储时程序闪退或长时间无响应 → 原因:自身堆内存不足 → 解决:调大 -Xmx

第一次分析一个 6GB 的 phd 文件,进度条跑到 80% 直接没了,终端里只留下一句 OutOfMemoryError。当时的分析机内存有 16GB,我以为是文件损坏,反复下载了三遍。后来才意识到,HA 的默认启动脚本给自身分配的内存很小,加载大 dump 时直接触顶。解决方式在 2.1 里写了:显式指定-Xmx,并且让这个值覆盖默认脚本配置。另外注意别把-Xmx调到接近物理内存上限,系统一旦开始用 swap,HA 会慢到像死机,反而不如小一点的内存快速拒绝。

这个坑的另一层启示是:分析转储和写代码不同,它不是越快越好。给 HA 配 6g 内存后,加载同一个文件大约需要 4 分钟,这期间 UI 无响应是正常的。判断是否“真死”还是“在算”,可以直接看系统 CPU 占用——如果 HA 进程的 CPU 持续高位,它在工作;如果 CPU 归零且界面不动,才需要考虑强制退出。

5.2 现象:标准 JDK 的 hprof 文件打不开 → 原因:phd 与 hprof 格式差异 → 解决:换用 IBM JVM 或切换工具

某次帮一个同事分析压力测试环境的问题,他用的是 OpenJDK 17,jmap导出的.hprof文件拖进 HA 后直接报格式错误。这个场景其实很常见:HA 对 IBM JVM 的 phd 格式支持最完整,对标准 HPROF 格式的兼容性是有限的。不少版本甚至没有图形化的导入向导,需要依赖命令行转换或直接拒绝打开。解决办法有两个分支:如果应用必须跑在标准 JDK 上,换用 Eclipse MAT 会更顺畅,它对 HPROF 的解析非常成熟;如果应用本身可以迁移到 IBM JVM(比如有些中间件本来就是跑 J9),那就用-Xdump:heap配置原生 phd,再把文件丢给 HA。

不要抱着“换个格式应该能打开”的侥幸心态反复试,格式转换经常会导致对象大小、引用信息的精度损失,算出来的 Retained Size 也可能失真。我的建议是:先确认当前 JVM 品牌和 dump 格式,再决定分析工具,顺序反了会白白浪费时间。

5.3 现象:每次分析结果里都有一堆不可达对象干扰判断 → 原因:dump 时堆里残留大量待回收实例 → 解决:dump 前触发一次 Full GC

有一次打开 dump 后,看到byte[]实例数高达几百万,心想这下抓到大头了,结果展开引用链一看,很多数组根本没有活跃引用,只是还没来得及被 GC 回收。原因是我在业务高峰期直接kill -3,那时候堆里堆满了各种请求产生的垃圾,dump 把这些垃圾原样拍了下来。HeapAnalyzer 在计算 retained size 时通常会把不可达对象排除掉,但树视图里那些对象的类节点依然存在,会干扰排序和计数。

解决方式是:在采样前主动触发一次 Full GC,把堆里可回收的东西清干净,再 dump。IBM JVM 上可以在触发 dump 前先通过管理接口或运维命令请求一次 GC;如果条件不允许,至少要意识到 dump 结果里“不可达对象”和“被引用对象”要分开看,不能一看到某个类实例数巨大就上头。如果有条件,尽量在低峰期、GC 后采样,比较干净。

5.4 现象:kill -3 之后找不到转储文件 → 原因:没配置 -Xdump:heap:file 或输出到了 stderr → 解决:显式配置文件名与触发事件

这个坑我见到不止一次:同事在线上执行kill -3 <pid>,然后去预期路径找.phd文件,文件夹是空的,或者只在日志里看到一段线程栈输出。原因很简单,IBM JVM 的kill -3默认行为是产生线程转储,并不会自动附带堆转储;堆转储必须由-Xdump:heap参数显式开启,并且配置了user事件才是可触发的。另外,即使触发了堆转储,如果没写file=指定路径,文件可能输出到 JVM 的工作目录或 stderr 指定的位置,不容易找到。解决方法是把file=写成一个绝对路径,并在启动后先做一次小流量验证,确认文件能正常生成再上线。配置里可以把events设为vmstart,user,启动时先产一个基线 dump,顺手验证路径权限和磁盘空间。

顺带提醒:确认磁盘空间是容易忽略的最后一环。一个 8GB 堆的 JVM,dump 文件可能膨胀到 8GB 以上;如果file=指向的目录没那么多空间,转储会静默失败,日志里还不一定有明显报错。建议至少预留堆大小的 1.5 倍磁盘余量。

5.5 现象:对象树展开后界面卡得没法操作 → 原因:没有先排序和过滤 → 解决:先排序列出 Top 节点,再用类名过滤

展开到第三层树节点,界面开始一帧一帧地动,点一下要等好几秒,最后只能强杀进程。这不是 HA 写得不好,而是对象树的总节点数可能达千万级,毫无目的地展开等于让界面渲染海量节点。解决方式已经在第 3 章讲过:先按 Retained Size 排序,只看前几十个节点,然后用过滤器输入类名或包名关键字,把视图范围收缩到具体业务包。展开时一次只展开一个分支,不要同时展开多个大节点。如果确实需要完整展开全树,转而用导出功能把对象树导出成文本文件,在文件里 grep,比在 GUI 里翻高效得多。

这里补一条经验:界面卡顿还有一个隐形原因是机器 DPI 缩放和远程桌面叠加导致的渲染负担,与分析本身无关。如果你通过远程桌面操作一台配置不低的机器仍然很卡,试试本地宿舍式的解决方案——把 dump 拷贝到本地分析机再开 HA,远程桌面只保留触发操作,体验会有明显改善。

6. 先看直方图再看树:一个让 HeapAnalyzer 事半功倍的排查习惯

6.1 命令行直方图先行:两分钟先看类实例数分布

别急着打开 GUI。拿到一份转储文件后,我现在的习惯是先用命令行工具看直方图,这在 IBM JVM 的-Xdump产物上仍然适用——你可以先分析一份小 dump 或用jmap -histo类似的思路找类分布,再决定值不值得上 HA。直方图能在一分钟内告诉你堆里实例数最多的前 20 个类是什么、占用字节数多少。这一步的价值不是替代 HA,而是给对象树的排查定方向:如果直方图里明显是某个业务类疯狂增长,你打开 HA 后直接过滤这个类,而不是从byte[]这种底层数组开始漫无目的地翻。

对比时也建议先打印两张直方图做 diff,找出数量变化最夸张的那几个类,再回到 HA 里看引用链。这个习惯帮我省掉了很多次无意义的全树展开操作,也让每次分析从“感觉哪里不对”变成“这里有数据支撑”。

6.2 把 dump 当成现场证据:命名、记录业务状态、和 GC 日志对照

另一个让我少走弯路的具体技巧是:把 dump 文件当成事故现场一样管理。文件名里带上时间戳和触发原因,比如heapdump_20250611_oom_before_restart.phd;分析时在旁边放上当时的 GC 日志、错误日志、连接数监控截图。有一次分析某模拟项目X的内存问题,光看对象树始终没想明白为什么某个缓存容器里堆积了大量请求上下文,后来对照 GC 日志发现那段时间 Full GC 频率暴增,而 Full GC 又反过来延长了缓存里对象的存活时间——两者互相放大,才导致了内存断崖。如果只看 dump,很容易只得出“缓存没设上限”这个次要结论,而漏掉 GC 压力这个加速器。

现在我的工作流程基本固定:先确认 dump 格式和触发原因,命令行走直方图,再让 HA 排序定位大头,最后用两次 dump 对比验证增长曲线。遇到非 IBM JVM 的环境也不再硬套 HA,而是果断切工具。这样下来,绝大多数内存问题都能在半天内定性。前阵子又一次凌晨排查,靠这套方法半小时内找到了那个只增不减的ThreadLocal缓存,回头想想,最值钱的不是工具本身,而是“先有数据再有结论”的排查习惯。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询