Skip to content

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 的失控。

工程上的两个必备动作:

  1. OOM 自动 dump:-XX:+HeapDumpOnOutOfMemoryError——事后分析的唯一现场,没有它,重启后什么都查不了;
  2. 容器时代看全局:进程被 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,每类配一个我遇到/排查过的典型场景:

  1. 堆 OOM:Java heap space——内存泄漏(无界缓存、ThreadLocal 不 remove、监听器堆积)或配置不足(一次 SELECT * 拉百万行)。dump + MAT 支配树定位。

  2. 元空间 OOM:Metaspace——CGLIB/反射动态生成类、Groovy 脚本、热部署 classloader 泄漏。jstat -gcmetacapacity 看趋势,Arthas sc -d 数类加载器。

  3. 栈溢出:StackOverflowError(严格说是 SOE)——递归无出口、JSON 深度嵌套解析;线程创建失败 unable to create new native thread——线程数超 OS 限制(ulimit -u)或栈总量耗尽,常见于无界 new Thread。

  4. 直接内存 OOM:Direct buffer memory——NIO/Netty 堆外没释放(没调 release、PooledByteBuf 泄漏)。配 -XX:MaxDirectMemorySize,Netty 开泄漏检测,NMT(-XX:NativeMemoryTracking)分析。

  5. GC 开销超限:GC overhead limit exceeded——老年代反复 Full GC 但回收不了几个字节(98% 时间 GC 换回 <2% 内存),堆实际已"假满",按堆泄漏排查。

  6. 虚拟机自身:OutOfMemoryError: Metaspace 之外还有 code cache 满(JIT 代码缓存 -XX:ReservedCodeCacheSize)等。

统一防线(军规):所有 JVM 必配 HeapDumpOnOutOfMemoryError + dump 路径;OOM 后快速退出交给容器拉起;dump 文件定期清理(一个 8G 堆的 dump 就 8G,别把磁盘打满造成二次故障)。