目录
[什么是 OOM](#什么是 OOM)
[常见 OOM 类型及产生原因](#常见 OOM 类型及产生原因)
[OOM 触发完整场景](#OOM 触发完整场景)
[OOM 问题定位步骤](#OOM 问题定位步骤)
[OOM 解决与优化方案](#OOM 解决与优化方案)
OOM异常***
你听过直接内存吗?
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常出现。
简记:
直接内存又称堆外内存,是虚拟机内存之外的内存,直接内存不会进行垃圾回收,当我们使用的时候要手动进行回收,不然就会导致内存泄漏。
什么是 OOM
全称 :java.lang.OutOfMemoryError,简称 OOM,意为「内存溢出」;
本质:JVM 无法申请到足够内存来存放新对象 / 数据,抛出的致命错误;
范围:不只是「堆溢出」,还包括方法区溢出、直接内存溢出等多种类型。
常见 OOM 类型及产生原因
1. 堆溢出(最常见)
-
错误信息 :
java.lang.OutOfMemoryError: Java heap space -
核心原因:
-
老年代满了:执行 Full GC 后仍无法容纳新对象 / 晋升对象;
-
大对象分配失败:超大对象无法放入 Eden 区,也无法放入老年代;
-
整体堆内存耗尽:新生代 + 老年代总内存达到
-Xmx上限,无法再扩容; -
内存泄漏:大量对象被长期引用(如缓存未清理、静态集合无限增长),无法被 GC 回收。
-
2. 方法区溢出
-
错误信息:
-
JDK7 及之前:
java.lang.OutOfMemoryError: PermGen space -
JDK8 及之后:
java.lang.OutOfMemoryError: Meta space
-
-
核心原因:
方法区(永久代 / 元空间)被类元数据占满,典型场景:
-
加载大量第三方 Jar 包(如 Tomcat 部署过多应用);
-
框架动态生成大量类(如 MyBatis Mapper、AOP 代理、Feign 接口、CGLIB 动态子类);
-
大量动态反射生成类,导致类加载器无法卸载,元数据持续累积。
-
3. 直接内存溢出
-
错误信息 :
java.lang.OutOfMemoryError: Direct Memory space -
核心原因 :NIO 等框架使用的直接内存 (Direct ByteBuffer)超过
-XX:MaxDirectMemorySize限制,未被及时释放。
OOM 触发完整场景
| 触发阶段 | OOM 类型 | 核心原因 |
|---|---|---|
| Minor GC 阶段 | Java heap space | 1. Minor GC 前老年代空间担保失败,Full GC 后仍无足够空间;2. 新生代 + 老年代整体内存耗尽,无法分配新对象 |
| Full GC 阶段 | Java heap space | Full GC 后堆内存仍无法容纳新对象 |
| 方法区阶段 | PermGen/Meta space | 方法区被类元数据占满,Full GC 无法释放足够空间 |
| 直接内存阶段 | Direct Memory space | 直接内存使用超过上限,未及时释放 |
OOM 问题定位步骤
第一步:生成堆转储文件(Heap Dump)
在 JVM 启动参数中添加:
-Xms30m -Xmx30m -XX:+HeapDumpOnOutOfMemoryError
-
作用:OOM 发生时自动生成
.hprof堆快照文件,记录当时内存状态; -
-Xms/-Xmx:设置堆大小(便于复现问题); -
-XX:+HeapDumpOnOutOfMemoryError:触发 OOM 时生成 dump 文件。
第二步:分析堆转储文件
使用工具打开 .hprof 文件:
-
IDEA 直接打开;
-
JDK 自带工具:
jhat、jvisualvm; -
专业工具:Eclipse MAT、YourKit、VisualVM 等。
第三步:定位问题根源
-
收集错误信息:查看日志,确认 OOM 类型(堆 / 方法区 / 直接内存);
-
分析堆快照:
-
查看内存占用最高的对象 / 数据结构;
-
检查是否存在内存泄漏:长时间存活的对象、未清理的缓存、静态集合无限增长;
-
确认是否存在大对象(如超大数组、集合);
-
-
检查内存使用趋势:是否存在持续的内存增长,未被 GC 回收;
-
定位代码位置:找到创建大量对象 / 动态类的业务代码或框架配置。
OOM 解决与优化方案
1. 堆溢出优化
-
增加堆内存 :调大
-Xmx参数(治标,需配合代码优化); -
修复内存泄漏:
-
清理无用对象引用(如手动清空集合、关闭资源);
-
合理设计缓存(设置过期时间、限制缓存大小);
-
避免静态集合无限存储对象;
-
-
减少大对象:拆分超大数组 / 集合,避免一次性加载全量数据;
-
优化对象创建:复用对象(如对象池),减少频繁创建 / 销毁。
2. 方法区溢出优化
-
增加元空间内存 :JDK8+ 调大
-XX:MaxMetaspaceSize; -
减少动态类生成:
-
避免过度使用动态代理(如 AOP、MyBatis Mapper);
-
优化框架配置,限制动态类数量;
-
及时卸载无用类(确保类加载器可被 GC 回收);
-
-
精简依赖:移除不必要的第三方 Jar 包,减少类加载数量。
3. 直接内存溢出优化
-
限制直接内存大小 :设置
-XX:MaxDirectMemorySize; -
及时释放直接内存 :手动调用
DirectBuffer.cleaner().clean()释放; -
避免过度使用 NIO 直接缓冲区,改用堆内缓冲区。
4. 通用优化
-
监控与预警:使用 JMX、Prometheus 等工具监控内存使用趋势;
-
性能测试:压测场景下验证内存稳定性,提前发现问题;
-
代码规范:避免无限增长的集合、长生命周期的局部变量、未关闭的资源。