线上 OOM 分析实战:JProfiler 与 Heap Dump 排查指南
Published in:2025-11-15 | category: 问题排查

本文梳理线上 OOM 问题的完整排查链路:从获取 Heap Dump,到用 JProfiler 定位内存大户,再到沿引用链追溯 GC Root,最后结合业务代码确认根因。

一、什么是 OOM

OOM(OutOfMemoryError)是 JVM 在无法分配足够内存时抛出的错误。常见类型:

OOM 类型原因
Java heap space堆内存不足,最常见的 OOM
GC overhead limit exceededGC 耗时过长但回收内存极少,JVM 判定陷入死循环
Metaspace元空间(类信息)占用过大,通常由类加载过多或框架动态生成类导致
Direct buffer memoryNIO 直接内存(堆外内存)耗尽
unable to create new native thread线程数超过操作系统限制

二、获取 Heap Dump 文件

分析 OOM 的第一步是拿到 Heap Dump 文件.hprof 或无后缀文件)。

2.1 JVM 启动参数自动生成

在启动参数中添加以下配置,OOM 发生时自动生成 dump 文件(强烈建议线上服务默认开启):

1
2
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump/

2.2 手动生成(对运行中的进程)

1
2
3
4
5
6
7
8
# 查找 Java 进程 PID
jps -l

# 使用 jmap 生成 dump 文件
jmap -dump:format=b,file=/path/to/heap.hprof <PID>

# 只导出存活对象(文件更小,但会触发 Full GC)
jmap -dump:live,format=b,file=/path/to/heap.hprof <PID>

2.3 通过 Arthas 生成

1
2
java -jar arthas-boot.jar <PID>
heapdump /path/to/heap.hprof

三、分析工具选择

拿到 dump 文件后,有两种主流的 JProfiler 分析方式:

方式优点缺点适用场景
JProfiler 软件功能完整,分析视图丰富需单独安装,上手略复杂深度分析,追踪复杂引用链
IDEA JProfiler 插件可直接定位到代码行,操作简便功能相对精简快速定位,日常排查

其他常用工具:Eclipse MAT(内存分析专用,适合分析超大 dump 文件)、VisualVM(轻量,免费开源)。


四、JProfiler 软件分析

4.1 打开 Dump 文件

打开 JProfiler → 启动中心 → 打开单个快照 → 选择 dump 文件。

常见问题:文件选择框中找不到 dump 文件

  • 原因:文件选择框有文件类型过滤,限制了可见的后缀,而 dump 文件可能无后缀。
  • 解决:在文件路径输入框中直接输入文件的完整路径或文件名,回车确认即可打开。

4.2 类视图(Heap Walker → Classes)

⚠️ 常见误区:排在最上面的类不一定是导致 OOM 的元凶!
JProfiler 默认按实例计数排序,实例多 ≠ 内存占用大。例如 ConcurrentHashMap$Node 实例数极多,但每个实例很小,总内存未必最大。

正确分析步骤:

  1. 按”大小(浅堆)”排序:快速看到哪些类的实例总体积最大。例如 char[]byte[] 加起来占用约 1.6G,明显异常。
  2. 计算保留大小:点击工具栏”计算预估的保留大小”,等待计算完成。
  3. 按”保留大小”排序:定位真正的内存大户。例如 WsFrameServer 保留大小达 1.5G,它是 Tomcat 处理 WebSocket 数据帧的核心类,由此定位到 WebSocket 连接未释放的问题。

“大小”与”保留大小”的区别

这是分析 OOM 最核心的两个概念:

指标别名定义
大小浅堆(Shallow Heap)对象本身在堆中占用的空间,不包括它引用的其他对象
保留大小保留堆(Retained Heap)对象被 GC 后能释放的总内存 = 对象本身 + 所有被它独家持有的对象

举例理解:

1
2
3
4
5
6
7
对象 A(浅堆 = 16B)
├── 引用 → 对象 B(浅堆 = 1MB,只被 A 引用)
└── 引用 → 对象 C(浅堆 = 2MB,同时被 A 和 D 引用)

A 的浅堆 = 16B
A 的保留堆 = 16B(A) + 1MB(B) ≈ 1MB
(C 不算,因为 D 也引用了 C,GC 掉 A 并不能释放 C)

分析 OOM 时,保留大小才是最关键的指标——它代表”释放这个对象能收回多少内存”。

4.3 最大对象视图(Biggest Objects)

模式排序方式适用场景局限
不分组(默认)按单个对象实例的保留大小怀疑是某个特定的单一超大对象(如一个超大 Map、List)无法发现海量小对象累积
按类分组同一类所有实例的保留大小总和定位海量小对象累积(如大量 Session、Request 未释放)

在类视图中双击某个类,会创建一个新的对象集(Object Set),仅包含该类的所有实例,便于进一步分析。

4.4 引用视图(References)

引用类型核心问题主要用途
传出引用“这个对象里面有什么?”分析巨大对象的内部构成
合并的传出引用“这个集合里装了哪些类型的对象?”分析集合类对象,避免视图臃肿
传入引用“是谁持有着这个对象,阻止它被 GC?”诊断内存泄漏最常用
合并的传入引用“哪些组件在共享这个对象?”追溯被多方引用的共享对象

分析内存泄漏时,沿着传入引用向上追溯,直到找到 GC Root,就能定位是谁”拎着”这个对象不放。


五、IDEA JProfiler 插件分析

将 dump 文件直接拖入 JProfiler 面板即可。核心功能入口:

功能说明
GC Roots所有直接作为 GC Root 的对象,按类型分组,用于诊断内存泄漏
Merged Paths(Dominator Tree)支配树。A 支配 B = 所有访问 B 的路径都必须经过 A;GC 掉 A,A 支配的所有对象都可回收
Packages按 Java 包分组统计的旭日图,快速定位问题所在的包范围

选中某个实例后,下方分析选项卡中,Shortest Paths(从 GC Roots 到此对象的最短引用路径)是定位内存泄漏根因的利器


六、OOM 分析通用思路

1
2
3
4
5
6
Step 1: 按"保留大小"排序 → 找到最占内存的类/对象
Step 2: 查看该类的实例数量 → 单个超大对象,还是海量小对象累积?
Step 3-A(单个大对象): 查看"传出引用",分析内部构成
Step 3-B(海量小对象): 查看"传入引用 / Shortest Paths",向上追溯到 GC Root
Step 4: 结合业务代码确认根因(连接池未关闭?缓存无限增长?连接泄漏?)
Step 5: 修复 + 在测试环境复现验证

七、常见 OOM 场景与排查方向

场景dump 特征排查方向
缓存无限增长大量 HashMap/ConcurrentHashMap 及 Entry检查缓存是否设置容量上限和过期策略
WebSocket/长连接泄漏WsFrameServerChannel 保留大小极大检查连接关闭逻辑、心跳超时处理
大量 Session 未释放海量 HttpSession 对象检查 Session 超时配置和销毁逻辑
大字符串/byte 数组char[]byte[] 占用极大检查大文件读取未分片、大 SQL 结果集未分页
线程池任务队列积压大量 Runnable/Task 对象检查线程池队列容量和任务执行速度
类加载过多(Metaspace)Metaspace 溢出,类数量异常多检查动态代理、脚本引擎、热部署是否产生类泄漏

八、总结

要点说明
核心指标保留大小(Retained Heap)是分析 OOM 最关键的指标
分析入口从”类视图 → 按保留大小排序”开始,快速缩小范围
引用追踪使用传入引用 / Shortest Paths,向上找到阻止 GC 的根因
工具选择快速定位用 IDEA 插件;深度分析用 JProfiler 软件或 MAT
预防措施设置 -XX:+HeapDumpOnOutOfMemoryError 确保 OOM 时自动保留现场
Prev:
《格鲁夫给经理人的第一课》读书笔记(一):产出导向的管理思维
Next:
Redis 问题排查:大 Key 与过期时间设计