内存快照对比分析需通过比对不同时刻的对象分布、引用关系和增长趋势来定位持续增长的内存来源,关键在于“变化量”,而非单次快照的静态大小。

内存快照对比分析不是简单地打开两个.hprof文件看谁更大,而是通过比对不同时刻的对象分布、引用关系和增长趋势,定位持续增长的内存来源。关键在于“变化量”,而非单次快照的静态快照。
选择合适的时间点生成快照
至少需要两个快照:一个在系统刚启动、负载平稳时(基线快照),另一个在内存明显上涨或OOM前几分钟(问题快照)。如果条件允许,可采集三个时间点(如启动后5分钟、30分钟、OOM前2分钟),便于观察增长斜率。
生产环境未预先配置参数时,可用jmap实时抓取:
- jps -l 查出目标Java进程PID
-
jmap -dump:live,format=b,file=heap_01.hprof
抓取当前存活对象快照(推荐加live参数,排除已标记但未回收的对象干扰) - 等待5–10分钟后,再执行一次jmap命令,生成heap_02.hprof
用MAT进行差异对比(Dominator Tree + Compare Basket)
Eclipse MAT是目前最主流的免费工具,其Compare Basket功能专为快照对比设计:
- 分别打开两个快照 → 右键任一快照 → Add to Compare Basket
- 切换到Compare Basket视图 → 点击Compare按钮
- 默认按“Shallow Heap”排序,重点关注Delta(差值)列显著为正、且Retained Heap增长剧烈的类,例如:ArrayList、HashMap、byte[]、自定义缓存类等
- 双击该行 → 进入Dominator Tree → 展开查看哪些实例在第二个快照中新增或显著变大
聚焦增长对象的引用链与归属线程
找到增长明显的对象后,不能只看它自己,要顺藤摸瓜:
- 右键该对象 → Path to GC Roots → with all references,确认它为何无法被回收(常见原因:被静态集合持有、被ThreadLocal引用、被监听器/回调长期注册)
- 在Dominator Tree中,点击对象旁的Thread Overview图标,查看创建该对象的线程栈,快速定位到具体业务方法或定时任务
- 若发现大量相同结构的byte[]或String增长,检查是否涉及日志打印、JSON序列化、流未关闭、响应体缓存等典型场景
辅助验证:结合GC日志与jstat趋势
快照对比结果需与运行时指标交叉印证:
- 检查GC日志中
FGC频率是否随快照时间推移而上升;若Full GC后老年代使用率仍居高不下,说明对象确实在“堆积” - 用
jstat -gc -t <pid> 5s</pid>持续采样,观察OU(老年代使用量)是否呈阶梯式上升,与快照采集时间点对齐 - 若对比发现某缓存Map实例Retained Heap增长1GB,但代码中该Map声明为局部变量,就要怀疑是否被意外提升为静态成员或放入了全局容器
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











