OOM分类与定位
OutOfMemoryError 不是一种问题,而是一组问题。不同 OOM 发生在不同内存区域,定位方式也不同。
# 1. Java heap space
java.lang.OutOfMemoryError: Java heap space
表示 Java 堆空间不足。
常见原因:
- 大集合无限增长。
- 缓存没有淘汰。
- 一次性加载大文件。
- 内存泄漏。
- 流量突增导致对象创建过快。
- 堆配置过小。
定位方式:
- 开启 OOM 自动 dump。
- 使用 MAT、JProfiler、VisualVM 分析堆转储。
- 查看最大对象、引用链、GC Roots。
# 2. GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded
表示 JVM 花大量时间做 GC,但回收效果很差。
本质通常还是堆压力过大。
常见原因:
- 内存泄漏。
- 堆太小。
- 老年代占满。
- 大量对象存活。
# 3. Metaspace
java.lang.OutOfMemoryError: Metaspace
表示元空间不足。
常见原因:
- 大量动态生成类。
- 类加载器泄漏。
- 热部署未释放。
- 代理类、脚本类不断增长。
定位方式:
- 查看类加载数量。
- 分析类加载器。
- 使用
jcmd VM.classloader_stats。 - 检查动态代理和热部署逻辑。
# 4. Direct buffer memory
java.lang.OutOfMemoryError: Direct buffer memory
表示直接内存不足。
常见原因:
- NIO direct buffer 创建过多。
- Netty ByteBuf 泄漏。
- 堆外内存限制过小。
- 引用未释放导致 Cleaner 无法回收。
定位方式:
- 检查
-XX:MaxDirectMemorySize。 - 使用 Native Memory Tracking。
- 查看框架堆外内存指标。
- 开启 Netty leak detector。
# 5. unable to create native thread
java.lang.OutOfMemoryError: unable to create native thread
表示无法创建新的系统线程。
原因可能是:
- 线程数过多。
- 系统线程数限制。
- 进程内存不足。
- 每个线程栈太大。
- 容器限制。
定位方式:
- 查看线程数量。
- 查看
-Xss。 - 查看系统
ulimit。 - 查看容器资源限制。
- 分析线程池是否失控。
# 6. Requested array size exceeds VM limit
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
表示请求创建的数组超过 JVM 支持的最大长度。
常见于:
- 计算数组长度溢出。
- 一次性读取超大文件。
- 错误地构造超大集合。
# 7. OOM 自动转储参数
生产环境常配置:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump
注意 dump 文件可能很大,要保证磁盘空间充足,并注意敏感数据保护。
# 8. 阶段小结
定位 OOM 的第一步是看错误信息,判断发生在哪个内存区域。堆 OOM 看对象引用链,元空间 OOM 看类加载器,直接内存 OOM 看堆外分配,线程 OOM 看线程数和系统限制。不要一看到 OOM 就盲目加 -Xmx。
# 9. OOM 定位决策树
出现 OOM / 进程被杀
│
├─ JVM 抛出 OutOfMemoryError?
│ │
│ ├─ Java heap space
│ │ └─ heap dump -> MAT -> GC Roots
│ │
│ ├─ GC overhead limit exceeded
│ │ └─ 看 GC 日志 + 堆对象存活
│ │
│ ├─ Metaspace
│ │ └─ 看类加载器、动态类、元空间参数
│ │
│ ├─ Direct buffer memory
│ │ └─ 看直接内存、Netty、NMT
│ │
│ └─ unable to create native thread
│ └─ 看线程数、Xss、ulimit、容器限制
│
└─ 没有 JVM OOM,容器 OOMKilled?
└─ 看进程 RSS、cgroup、堆外、线程栈、系统事件
这棵树的重点是:先分类型,再找证据。不同 OOM 的处理方式完全不同。
# 10. OOM 现场保护
生产环境建议:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump
-XX:ErrorFile=/data/logs/hs_err_pid%p.log
同时要注意:
- dump 目录磁盘空间。
- dump 文件敏感数据。
- OOM 后是否自动重启导致现场丢失。
- 容器 OOMKilled 时 JVM 可能来不及 dump。
# 11. 不同 OOM 的修复方向
| OOM 类型 | 临时缓解 | 根因修复 |
|---|---|---|
| Java heap space | 扩堆、限流、重启 | 修复泄漏、限制缓存、流式处理 |
| Metaspace | 增大元空间 | 修复类加载器泄漏、减少动态类 |
| Direct buffer memory | 增大直接内存、重启 | 释放 ByteBuf、复用 buffer |
| native thread | 降低线程数、扩资源 | 线程池治理、降低 -Xss |
| 容器 OOMKilled | 扩容或降堆 | 做总内存预算 |
# 12. Tips 快问快答
Q:OOM 后 dump 一定能生成吗?
A:不一定。容器 OOMKilled、磁盘不足、权限问题都可能导致 dump 失败。
Q:GC overhead limit exceeded 是 GC 问题吗?
A:表面是 GC 开销过高,本质通常是堆中存活对象太多或堆太小。
Q:OOM 一定要重启吗?
A:线上恢复可能需要重启,但重启前尽量保留 dump、日志和指标,否则根因会丢失。
Q:为什么加大堆后问题过几天又出现?
A:如果根因是泄漏,加堆只是延迟爆发,不是修复。
上次更新: 2026/06/24, 16:03:01