JVM 有那几种情况会产生 OOM(内存溢出)?
- 标签:后端、Java、JVM
🏛 业务架构师
OOM 的类型按内存区域分,每类对应不同的业务根因:
- 堆 OOM(java.lang.OutOfMemoryError: Java heap space):最常见。业务根因:一次拉取超大数据(
select *千万行、导出无分页)、缓存无上限增长(本地 Map 只进不出)、内存泄漏(静态集合持有对象)——九成是业务代码的内存纪律问题; - 元空间 OOM(Metaspace):动态类生成失控——CGLIB/反射代理每次 new、Groovy/EL 脚本反复编译、热部署残留。框架误用的信号(应该复用的代理被循环创建);
- 栈溢出(StackOverflowError,广义 OOM):深递归——树形结构解析没控制深度;被恶意构造的深层嵌套 JSON/XML 打崩服务是真实攻击面(解析深度必须设上限);
- 直接内存 OOM(Direct buffer):Netty/堆外缓冲——池配置过大或堆外泄漏(这个最隐蔽:堆监控一切正常,进程 RSS 涨到被 OOMKilled);
- GC 开销超限(GC overhead limit exceeded):堆快满、GC 空转——慢性泄漏的终末阶段;
- 无法创建线程:线程数超系统限制——无界线程池/每次请求 new Thread 的失控。
工程上的两个必备动作:
- OOM 自动 dump:
-XX:+HeapDumpOnOutOfMemoryError——事后分析的唯一现场,没有它,重启后什么都查不了; - 容器时代看全局:进程被 OOMKilled(内核杀,无 Java 异常)= JVM 总内存(堆+元+栈+堆外)超容器 limit——内存预算表是发布规范的一部分。
一句话:OOM 类型是业务问题的分类标签——看懂报错分区,就等于拿到了根因的检索词。
🏗 系统架构师
OOM 的类型按内存区域定位(这题的价值就在"按区域快速分类排查"):
- Java heap space:堆耗尽——内存泄漏(静态集合持有对象、ThreadLocal 未 remove、监听器未注销)、缓存无界、大查询一次拉全量(select 千万行到 List)、堆配置不足。排查:
-XX:+HeapDumpOnOutOfMemoryError拿 dump → MAT 看支配树与大对象来源; - GC overhead limit exceeded:98% 时间 GC 却回收不到 2% 内存,堆接近满的先兆,同 heap 排查;
- Metaspace / PermGen space:类元数据爆——CGLib/反射大量生成类、热部署 ClassLoader 未释放(每次 reload 泄漏一个 loader)、Groovy 动态脚本。排查:jmap -clstats 看 loader 数量;
- Unable to create new native thread:线程数超系统限制(ulimit)或内存不足以再开线程栈——线程泄漏(未收敛的无界线程池、未关闭连接)、容器 PID limit。排查:jstack 数线程分类;
- StackOverflowError:递归无出口/过深(严格说是 SOE 不是 OOM);
- Direct buffer memory:NIO DirectBuffer/Netty 堆外内存耗尽——未 release、-XX:MaxDirectMemorySize 不够;
- 进程被 OS Kill(OOM Killer):RSS 超容器 limit——堆 + 元空间 + 堆外 + 线程栈总账超了,容器内最常见。
我的方法论:先看异常类型定区域 → dump/NMT/jstack 三件套取证 → 定位到代码模式(泄漏 or 配置)→ 修复后加对应监控(堆水位、类加载数、线程数、RSS)防复发。
💻 全栈开发专家
6 类 OOM,每类配一个我遇到/排查过的典型场景:
堆 OOM:
Java heap space——内存泄漏(无界缓存、ThreadLocal 不 remove、监听器堆积)或配置不足(一次SELECT *拉百万行)。dump + MAT 支配树定位。元空间 OOM:
Metaspace——CGLIB/反射动态生成类、Groovy 脚本、热部署 classloader 泄漏。jstat -gcmetacapacity看趋势,Arthassc -d数类加载器。栈溢出:
StackOverflowError(严格说是 SOE)——递归无出口、JSON 深度嵌套解析;线程创建失败unable to create new native thread——线程数超 OS 限制(ulimit -u)或栈总量耗尽,常见于无界new Thread。直接内存 OOM:
Direct buffer memory——NIO/Netty 堆外没释放(没调 release、PooledByteBuf 泄漏)。配-XX:MaxDirectMemorySize,Netty 开泄漏检测,NMT(-XX:NativeMemoryTracking)分析。GC 开销超限:
GC overhead limit exceeded——老年代反复 Full GC 但回收不了几个字节(98% 时间 GC 换回 <2% 内存),堆实际已"假满",按堆泄漏排查。虚拟机自身:
OutOfMemoryError: Metaspace之外还有 code cache 满(JIT 代码缓存-XX:ReservedCodeCacheSize)等。
统一防线(军规):所有 JVM 必配 HeapDumpOnOutOfMemoryError + dump 路径;OOM 后快速退出交给容器拉起;dump 文件定期清理(一个 8G 堆的 dump 就 8G,别把磁盘打满造成二次故障)。