Java 内存溢出(OutOfMemoryError,简称 OOM) 是 JVM 在申请内存失败时抛出的严重错误,通常伴随内存耗尽或垃圾回收无法回收足够空间。OOM 不一定是内存泄漏(Memory Leak),也可能是配置不当或代码一次性分配过多内存。以下是 Java 最常见的 OOM 类型、原因及解决方法(基于 Java 8+ 版本):常见 OOM 类型及原因对比表
| OOM 类型 | 常见错误信息示例 | 主要原因 | 解决思路(优先级从高到低) |
|---|---|---|---|
| Java heap space | java.lang.OutOfMemoryError: Java heap space | 1. 堆内存不足(-Xmx 太小) 2. 内存泄漏(对象无法回收) 3. 一次性创建超大对象 | 1. 分析 heap dump 找泄漏 2. 优化代码 3. 适当增大 -Xmx |
| GC overhead limit exceeded | java.lang.OutOfMemoryError: GC overhead limit exceeded | GC 花费 >98% 时间却只回收 <2% 内存(JDK 默认开启) | 1. 优化代码/内存泄漏 2. 禁用此限制(-XX:-UseGCOverheadLimit) 3. 增大堆 |
| PermGen space(JDK 7及以下) | java.lang.OutOfMemoryError: PermGen space | 永久代空间不足(类加载过多) | 1. 增大 -XX:MaxPermSize 2. 升级到 JDK 8+(使用 Metaspace) |
| Metaspace(JDK 8+) | java.lang.OutOfMemoryError: Metaspace | 元空间不足(动态类加载、CGLIB、Spring 频繁重载等) | 1. 增大 -XX:MaxMetaspaceSize 2. 减少动态代理/类加载 3. 避免频繁热部署 |
| unable to create new native thread | java.lang.OutOfMemoryError: unable to create new native thread | 系统无法创建新线程(线程数过多或 -Xss 太大) | 1. 减小 -Xss(默认 1MB) 2. 优化线程池 3. 调高系统 ulimit |
| Direct buffer memory | java.lang.OutOfMemoryError: Direct buffer memory | DirectByteBuffer 分配过多(Netty、NIO 等) | 1. 增大 -XX:MaxDirectMemorySize 2. 及时释放 DirectBuffer |
| Requested array size exceeds VM limit | java.lang.OutOfMemoryError: Requested array size exceeds VM limit | 试图分配超大数组(> Integer.MAX_VALUE) | 优化算法,分块处理数据 |
内存溢出最常见场景及解决方法(Top 8)
| 排名 | 场景 | 典型表现 | 解决方法 |
|---|---|---|---|
| 1 | 内存泄漏(最常见、最难定位) | 内存随时间持续上涨,Full GC 频繁且回收很少 | 1. jmap -dump 或 -XX:+HeapDumpOnOutOfMemoryError 生成 heap dump 2. 用 MAT/VisualVM 分析引用链 3. 修复泄漏代码 |
| 2 | 大对象分配(大数组、超大 String、一次加载大文件) | 瞬间 OOM | 分块处理、流式读取、避免一次性加载全部数据 |
| 3 | 集合类未清理(HashMap、ArrayList、ThreadLocal 等) | 静态/全局集合不断 add 却不 remove | 使用完及时 clear(),或用 WeakHashMap / ThreadLocal.remove() |
| 4 | ThreadLocal 使用不当 | 线程池复用线程 + ThreadLocal 未 remove() | 每次使用完必须调用 remove() |
| 5 | 线程数过多(线程池无界、new Thread 疯狂创建) | unable to create new native thread | 使用有界线程池(ThreadPoolExecutor),降低 -Xss |
| 6 | 频繁动态代理/类加载(Spring AOP、CGLIB、热部署) | Metaspace 耗尽 | 控制代理生成、增大 Metaspace、避免频繁重载类 |
| 7 | DirectByteBuffer 分配过多(Netty、NIO) | Direct buffer memory | 手动释放(Cleaner),或增大 -XX:MaxDirectMemorySize |
| 8 | 配置不当(堆太小、GC 参数不合理) | 业务正常但压测/高峰 OOM | 调优 -Xms/-Xmx(建议 -Xms = -Xmx),选择合适 GC(G1/ZGC) |
排查 OOM 的标准步骤(生产环境推荐)
- 开启自动 dump(推荐永久开启)bash
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump - 生成 heap dump(如果未自动生成)bash
jmap -dump:live,format=b,file=heapdump.hprof <pid> - 分析工具(强烈推荐)
- MAT(Eclipse Memory Analyzer):最强大,自动找泄漏嫌疑
- jvisualvm / jmc:Oracle 官方工具,适合快速查看
- jhat:JDK 自带(不推荐,内存占用大)
- GCeasy / GCTime:在线分析 GC 日志
- 查看 GC 日志(-XX:+PrintGCDetails -Xloggc:gc.log)
- 观察 Full GC 频率和回收量
- 老年代是否持续上涨 → 基本确定内存泄漏
- 快速临时缓解
- 增大堆内存(-Xmx)
- 重启应用(治标不治本)
总结:治本 vs 治标
| 方式 | 适用场景 | 是否治本 |
|---|---|---|
| 增大 -Xmx | 配置过小、临时高峰 | 否 |
| 优化代码 | 内存泄漏、大对象分配 | 是 |
| 调优 GC | GC 频繁但回收少 | 部分 |
| 使用弱引用 | 缓存场景 | 部分 |
记住:“内存泄漏 + 无限增长” 是 OOM 的头号杀手。发现 OOM 后,优先 dump 内存快照 → 用 MAT 分析,才是最快、最准确的定位方式。如果有具体的 OOM 错误信息、GC 日志或业务场景,欢迎贴出来,我可以帮你进一步分析!