oom需先据错误后缀区分类型:java heap space→堆内存泄漏;metaspace→类加载器泄漏;unable to create new native thread→线程数超限;direct buffer memory→堆外内存泄漏;gc overhead limit exceeded→gc失效型内存泄漏。

Java 中 OOM(OutOfMemoryError)不是普通异常,而是 JVM 内存耗尽、GC 无力回收时抛出的致命错误。分析与定位关键在“分类型、看现场、追根源”,不能只盯着堆内存——不同 OOM 类型对应不同内存区域和排查路径。
先确认 OOM 类型,看报错信息最直接
日志里那行 java.lang.OutOfMemoryError: xxx 就是第一线索:
- Java heap space:堆内存不够,90% 的 OOM 属于这一类,重点查对象堆积或泄漏
- Metaspace:元空间爆了,常见于大量动态代理(如 CGLIB)、频繁热部署、类加载器未释放
- Direct buffer memory:直接内存超限,多见于 Netty、NIO 大量使用 ByteBuffer.allocateDirect() 且未 clean()
- Unable to create new native thread:线程数过多,可能线程池配置失控或线程未正确回收
- GC overhead limit exceeded:GC 花了 98% 时间却只回收不到 2% 内存,说明堆里全是“活垃圾”,大概率是内存泄漏
快速判断是否真 OOM:用 jstat 看 GC 行为
别急着 dump,先连上问题进程跑一句:
jstat -gc -h10
- FGC 每分钟几十次 + 老年代使用率持续 95% 以上 → 基本确定堆 OOM 在发生
- YGC 很快但对象不断进老年代 → 可能对象生命周期过长或 Survivor 区太小
- GC 后老年代不降反升 → 强烈提示内存泄漏
抓取并分析内存快照:MAT 是核心工具
确保启动参数含 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps,OOM 触发后自动生成 hprof 文件。
用 Eclipse MAT 打开后,按顺序做三件事:
- 点 Leak Suspects Report:自动标出最可疑的泄漏链,优先看
- 打开 Histogram,按 "Retained Heap" 排序:找占用最大的类(比如 ArrayList、HashMap、byte[])
- 右键目标类 → Merge Shortest Paths to GC Roots(排除弱/软引用):看清谁在强持有这些对象,比如静态 Map、缓存未清理、监听器未注销
结合代码和场景验证根因
工具只给线索,最终得靠人来闭环:
- 查到大量 byte[] 占内存?→ 看是不是全量查数据库、大文件读取没分页、日志输出了超长字符串
- 发现 Proxy 类暴增?→ 检查 Spring AOP 切面范围是否过大、CGLIB 代理是否重复创建
- 线程数飙升?→ 查线程池是否无界、异步任务是否漏了 shutdown、是否有 ThreadLocal 未 remove()
- DirectByteBuffer 不释放?→ 确认 Netty 是否用了 ResourceLeakDetector、NIO 代码是否调了 cleaner().clean()
不复杂但容易忽略:很多 OOM 其实是配置和习惯问题——堆设太小、元空间没限制、线程池用 Executors.newCachedThreadPool、缓存没设上限。定位之后,顺手补上监控(如 Prometheus + JVM Exporter)和告警阈值,比等炸了再救更省力。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











