一、OOM 问题本质与分类
OOM(Out Of Memory) 表示 JVM 内存不足以分配对象空间。根据 JVM 内存区域划分,常见 OOM 类型包括:
- 堆内存溢出(
java.lang.OutOfMemoryError: Java heap space)
对象分配超过堆内存限制。 - 元空间溢出(
java.lang.OutOfMemoryError: Metaspace)
类元数据(如类定义、方法信息)超过元空间限制。 - 直接内存溢出(
java.lang.OutOfMemoryError: Direct buffer memory)
使用 NIO 分配的堆外内存超出限制。 - 栈溢出(
java.lang.StackOverflowError)
线程调用栈深度超过限制。
二、详细排查步骤(基于图片流程扩展)
1. 导出内存堆栈(Heap Dump)
核心目标:获取 JVM 内存中对象分配的完整快照。
(1)手动导出 Heap Dump
# 命令格式:jmap -dump:format=b,file=<文件名> <PID>
jmap -dump:format=b,file=oom_heapdump.hprof 1234
-
jmap:JDK 自带的堆内存分析工具。 -
format=b:二进制格式导出,兼容 MAT 等工具。 -
file=oom_heapdump.hprof:生成的堆转储文件名。 -
1234:Java 进程的 PID(通过jps或ps -ef | grep java查看)。
(2)JVM 参数自动导出
在启动脚本中添加参数:
-XX:+HeapDumpOnOutOfMemoryError # 发生 OOM 时自动生成 Heap Dump
-XX:HeapDumpPath=/path/to/dumps # 指定 Heap Dump 存储路径
-XX:OnOutOfMemoryError="kill -9 %p" # 可选:OOM 后终止进程(防止僵尸进程)
(3)解析 Heap Dump 基本信息
jhat -port 7000 oom_heapdump.hprof # 启动 HTTP 服务器分析 Heap Dump
访问 http://localhost:7000 查看对象分布,但功能较弱,推荐使用 MAT。
2. 使用 MAT(Memory Analyzer Tool)深度分析
MAT 是 Eclipse 基金会开发的 Java 内存分析工具,支持可视化分析堆转储文件。
(1)安装与启动
- 下载地址:Downloads | The Eclipse Foundation
- 导入 Heap Dump 文件:
File → Open Heap Dump
(2)关键分析功能
| 功能 | 作用 | 操作路径 |
|---|---|---|
| Histogram(直方图) | 按类统计对象数量及占用内存 | OverView → Histogram |
| Dominator Tree(支配树) | 显示哪些对象直接占用最多内存 | OverView → Dominator Tree |
| Leak Suspects(泄漏嫌疑) | 自动生成内存泄漏报告 | Reports → Leak Suspects |
| Path to GC Roots(到 GC Roots 的路径) | 查看对象到 GC Roots 的引用链,判断是否被强引用持有 | 右键对象 → Path to GC Roots |
(3)实战分析示例
-
定位大对象:
- 在
Dominator Tree中按Retained Heap(对象及其引用链占用的总内存)排序。 - 查找占用内存异常的类(如自定义的缓存类、集合类)。
- 在
-
分析引用链:
- 右键可疑对象 →
Merge Shortest Paths to GC Roots→exclude weak/soft references。 - GC Roots:JVM 中不会被垃圾回收的对象(如静态变量、活动线程栈中的局部变量)。
- 右键可疑对象 →
-
内存泄漏确认:
- 如果发现大量对象被
ThreadLocal、静态Map或未关闭的资源(如数据库连接)引用,则存在内存泄漏。
- 如果发现大量对象被
3. 结合代码定位问题根源
通过 MAT 找到可疑对象后,需在代码中定位具体逻辑。
(1)代码示例与注释
public class MemoryLeakDemo {
private static Map<Long, byte[]> cache = new HashMap<>(); // 静态 Map 导致对象无法释放
public void processRequest(long userId) {
byte[] data = new byte[1024 * 1024]; // 分配 1MB 内存
cache.put(userId, data); // 数据存入静态 Map
// 业务逻辑处理...
}
}
- 问题点 1:
static Map是 GC Root,导致所有存入的byte[]无法回收。 - 问题点 2:未清理过期数据,
cache无限增长。
(2)修复方案
public class FixedMemoryDemo {
private static Map<Long, byte[]> cache = new WeakHashMap<>(); // 使用弱引用 Map
public void processRequest(long userId) {
byte[] data = new byte[1024 * 1024];
cache.put(userId, data);
// 业务逻辑处理...
// 添加清理逻辑(如 LRU 策略)
if (cache.size() > 1000) {
cache.remove(cache.keySet().iterator().next());
}
}
}
- 改进点 1:
WeakHashMap的键是弱引用,当键不再被强引用时,条目自动删除。 - 改进点 2:手动控制缓存大小,防止无限增长。
三、高级排查技巧
1. JVM 内存参数调优
-Xms4g -Xmx4g # 堆内存初始值=最大值,避免动态扩容
-XX:MetaspaceSize=256m # 元空间初始大小
-XX:MaxMetaspaceSize=512m
-XX:MaxDirectMemorySize=1g # 限制直接内存
2. 监控工具辅助
- VisualVM:实时监控堆内存、线程、CPU。
- Arthas:在线诊断工具,支持动态查看对象分布:
dashboard # 查看实时内存/线程状态 heapdump --live /tmp/dump.hprof # 导出存活对象堆转储
3. 代码层预防
- 避免静态集合类:尽量使用局部变量或弱引用(
WeakReference)。 - 及时关闭资源:数据库连接、文件流等必须显式关闭。
- 限制缓存大小:使用
Guava Cache或Caffeine并设置过期策略。


被折叠的 条评论
为什么被折叠?



