堆内存快照对比分析需确保同口径、可追溯、有参照:优先用jmap -dump:live导出存活对象,至少采集3个时间点快照,避免混用不同JVM参数或JDK版本;MAT中通过Compare by class模式识别增长类,结合ClassLoader Explorer、集合路径和大对象生命周期定向分析;命令行可用jmap -histo和-clstats快速验证。

堆内存快照对比分析不是简单打开两个文件看数字差异,而是通过多时间点快照的定量比对,识别对象增长趋势、类加载异常、泄漏路径演化等深层问题。核心在于“同口径、可追溯、有参照”——必须确保快照生成条件一致、分析维度统一、引用链可回溯。
选择合适的时间点与快照类型
对比的前提是快照具有可比性:
- 优先使用
jmap -dump:live,format=b,file=heap-20261012-1400.hprof <pid>导出存活对象,避免已死亡但未回收对象干扰统计; - 至少采集 3 个时间点:问题初现(baseline)、明显恶化(spike)、Full GC 后(recovery),例如间隔 5–10 分钟;
- 若排查类加载器问题,需同步采集
jmap -clstats <pid>输出,作为类加载器维度的辅助对照; - 避免混用不同 JVM 参数(如是否开启压缩类空间、元空间大小)或不同版本 JDK 生成的快照,否则类元数据结构可能不兼容。
在 MAT 中执行结构化对比
Eclipse MAT 是目前最成熟的对比分析工具,支持原生快照比对功能:
- 启动 MAT 后,依次导入两个快照(File → Import → Heap Dump → Compare two heap dumps);
- 选择“Compare by class”模式,MAT 自动计算每个类的实例数差值、浅堆/保留堆变化量,并高亮增长 >20% 或绝对增量 >5000 的类;
- 重点关注接口/抽象类的实现类分布变化:例如
java.util.List实例总数微增,但com.company.CustomListImpl增长 3 倍,说明多态实现类被异常复用或重复加载; - 右键某类 → “Merge Shortest Paths to GC Roots with objects from another dump”,可查看该类新增实例是否被同一组静态容器或线程局部变量持有着——这是泄漏演化的关键证据。
聚焦三类关键对比维度
不要泛泛看“谁占内存多”,要带着问题定向比对:
-
类加载器维度:在 “ClassLoader Explorer” 中分别展开两个快照,对比各 ClassLoader 加载的类数量、其下
java.lang.Class实例数。若插件类加载器 A 在第二份快照中多加载了 127 个com.service.*类,而 AppClassLoader 对应类数不变,说明存在热部署未清理或类加载越界; -
集合膨胀路径:对高频增长的
HashMap或ConcurrentHashMap,分别执行 “Path to GC Roots” 并勾选 “exclude weak/soft references”,比对根路径中是否新增了未注销的监听器、未关闭的资源句柄或 ThreadLocal 持有; -
大对象生命周期:用 Dominator Tree 定位保留堆 Top 5 对象,在两份快照中查看其 “Retained Heap” 变化率。若某个缓存
byte[]的保留堆从 8MB 增至 210MB,且其父级CacheManager实例地址未变,说明该对象持续扩容而未淘汰。
结合命令行做轻量级快速验证
不必每次都开 MAT,日常巡检可用 jmap + 文本比对快速筛查:
- 导出直方图:
jmap -histo:live 12345 > histo-1400.txt和jmap -histo:live 12345 > histo-1410.txt; - 提取业务关键类行(如
OrderService,PaymentProcessor),用awk '{print $1,$2,$3}'提取序号、实例数、字节数,再用diff或 Excel 计算增长率; - 对类加载器统计:
jmap -clstats 12345 2>/dev/null | grep -E "(^class|^total)",对比相同 ClassLoader 的 loadedClassCount 是否持续上升——上升即表明类未卸载,存在泄漏风险。


















