一句话总结
OOM 排查四步法:① 看日志(确认 OOM 类型和发生时机)→ ② 导 dump(jmap -dump 或 -XX:+HeapDumpOnOutOfMemoryError 自动导出)→ ③ 分析 dump(MAT/Eclipse Memory Analyzer 查看大对象、GC Roots 引用链、泄漏嫌疑报告)→ ④ 定位修复(根据分析结果修复代码或调整 JVM 参数)。常见 OOM 类型:Java heap space、GC overhead limit exceeded、Metaspace、Direct buffer memory。
中级深入
自动导出 dump 配置
# OOM 时自动导出堆 dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump/
# 手动导出堆 dump
jmap -dump:live,format=b,file=heap.hprof <pid>
# live 参数:导出前先触发一次 Full GC,只保留存活对象
# 查看堆中对象统计(Top N)
jmap -histo:live <pid> | head -20
MAT 分析四步法
// ① 打开 dump 文件 → 选择 Leak Suspects Report
// ② 查看 Pie Chart:哪个对象占用最多内存
// ③ 查看 Dominator Tree:按保留大小排序,找大对象
// ④ 查看 GC Roots 路径:为什么这个对象不能被回收
// MAT 关键概念:
// Shallow Heap:对象自身占用的内存
// Retained Heap:对象自身 + 它引用的所有对象的内存(回收它能释放的内存)
// Dominator Tree:支配树,展示对象的保留关系
常见内存泄漏场景
// ① ThreadLocal 未 remove
ThreadLocal<User> tl = new ThreadLocal<>();
tl.set(new User()); // 使用完未调用 tl.remove()
// ② 集合类持有对象引用
static List<Object> list = new ArrayList<>();
// 静态集合不断 add,对象永远不会被回收
// ③ 内部类持有外部类引用
class Outer {
// 非静态内部类默认持有外部类引用
// 如果内部类被长生命周期对象持有,外部类也无法回收
}
// ④ 资源未关闭
InputStream is = new FileInputStream("file");
// 未调用 close(),关联的 native 内存未释放
高级拓展
GC Overhead Limit Exceeded 详解
// 触发条件:GC 时间超过总运行时间的 98%,但回收的内存不到 2%
// 本质:JVM 在"垂死挣扎",大部分时间都在 GC,但几乎回收不了内存
// 解决方案:
// 1. 增大堆内存 -Xmx
// 2. 排查内存泄漏(dump 分析)
// 3. 检查是否有超大对象
// 4. 关闭此检查(不推荐):-XX:-UseGCOverheadLimit
直接内存溢出排查
// 直接内存不在堆中,dump 文件看不到
// 排查方法:
// 1. 检查 -XX:MaxDirectMemorySize 设置
// 2. 检查是否有大量 DirectByteBuffer 未释放
// 3. 使用 jcmd <pid> VM.native_memory summary 查看 native 内存
// 常见原因:
// - NIO 使用 DirectByteBuffer 未及时释放
// - Netty 等框架使用堆外内存池
// - Unsafe.allocateMemory() 未释放
实战场景
完整排查流程
# 1. 查看进程
jps -l
# 12345 com.example.MyApplication
# 2. 查看内存和 GC 情况
jstat -gc 12345 1000 10
# 观察 EU、OU 是否持续增长,FGC 是否频繁
# 3. 查看对象统计
jmap -histo:live 12345 | head -30
# 看哪个类的实例数量异常多
# 4. 导出 dump
jmap -dump:live,format=b,file=heap.hprof 12345
# 5. MAT 分析
# 打开 heap.hprof → Leak Suspects Report
# 定位到泄漏对象 → 查看 GC Roots 路径 → 修复代码
内存泄漏代码示例
// 经典泄漏:HashMap 的 key 使用可变对象
public class MemoryLeak {
public static void main(String[] args) {
Map<User, String> map = new HashMap<>();
User user = new User("张三");
map.put(user, "value");
user.setName("李四"); // 修改了 key 的 hashCode!
map.get(user); // null!找不到,但对象还在 map 中,无法回收
}
}
面试模拟
Q:线上 OOM 如何排查?
A:1) 看日志确认 OOM 类型;2) 导 dump(jmap 或自动导出);3) MAT 分析:Leak Suspects Report → Dominator Tree 找大对象 → GC Roots 路径定位泄漏点;4) 修复:代码修复或调整 JVM 参数。同时用 jstat 观察 GC 趋势,jstack 查看是否有死锁或线程堆积。
Q:常见的内存泄漏场景有哪些?
A:1) ThreadLocal 未 remove;2) 静态集合不断 add 对象;3) 非静态内部类持有外部类引用;4) 资源未关闭(连接、流);5) 监听器/回调未注销;6) HashMap 使用可变对象作 key 导致无法 remove。
Q:GC overhead limit exceeded 是什么?怎么解决?
A:GC 耗时超过总运行时间的 98% 但回收不到 2% 内存时触发。说明 JVM 在"垂死挣扎"。解决:1) 增大堆内存;2) 排查内存泄漏;3) 检查是否有超大对象;4) 优化代码减少对象创建。不建议关闭此检查,那是掩耳盗铃。