彻底吃透 Java OOM 异常:从原理、场景、排查到解决方案全攻略

张开发
2026/5/10 21:39:44 15 分钟阅读

分享文章

彻底吃透 Java OOM 异常:从原理、场景、排查到解决方案全攻略
在 Java 后端开发里OOMOutOfMemoryError绝对是线上最让人 “头皮发麻” 的问题之一。它不像普通异常那样好定位往往服务跑着跑着突然崩掉日志寥寥几句让人无从下手。这篇文章就把 OOM 讲得通透、详细、能直接用于实战从是什么、为什么、有哪些类型、怎么排查、怎么根治一次性讲全。一、什么是 OOMOOM 全称OutOfMemoryError意思是JVM 无法为新对象分配内存且垃圾回收也无法腾出足够空间最终抛出的致命错误。重点理解三句话OOM 是Error不是 Exception一旦出现应用基本不可用。不是 “内存不够”而是内存被占满、且回收不了。90% 的 OOM 不是 JVM 内存太小而是代码问题。二、OOM 出现的根本原因一句话总结程序不断创建对象 → 对象一直被引用 → GC 无法回收 → 内存被撑爆 → 新对象无处安放 → OOM常见根源内存泄漏对象不用了但一直被持有GC 无法回收一次性加载过多数据不分页查全表、大文件全量读入内存资源未关闭连接、流、线程池滥用静态集合无限添加对象JVM 参数不合理堆、元空间设置过小动态生成大量类导致元空间溢出三、JVM 内存区域与 OOM 的关系Java 虚拟机把内存分成不同区域每个区域都可能 OOMHeap 堆存放对象实例 →最常见 OOMMetaspace 元空间存放类信息、方法、常量、代理类虚拟机栈方法调用栈 → 栈溢出本地方法栈Native 方法使用直接内存NIO 使用的堆外内存程序计数器唯一不会 OOM 的区域下面我们逐个讲最常出现的 6 种 OOM。四、6 种最经典 OOM 场景详解1. 堆内存溢出Java heap space错误信息java.lang.OutOfMemoryError: Java heap space原因堆内存被占满不断创建对象且都被强引用无法 GC。典型代码ListObject list new ArrayList(); while (true) { list.add(new Object()); }真实业务场景不分页查询数据库全表数据大文件一次性全部加载到内存死循环创建对象静态集合无限添加数据这是线上最常见的 OOM。2. 元空间溢出Metaspace错误信息java.lang.OutOfMemoryError: Metaspace原因加载的类太多元空间放不下。常见场景大量动态代理CGLIB、MyBatis、Spring 代理反射频繁生成类热部署过多自定义类加载器未正确释放JVM 参数-XX:MetaspaceSize128m -XX:MaxMetaspaceSize256m3. 直接内存溢出Direct buffer memory错误信息java.lang.OutOfMemoryError: Direct buffer memory原因NIO 使用ByteBuffer.allocateDirect()申请的堆外内存耗尽。常见场景Netty、MINA 等 NIO 框架大文件上传下载、流媒体处理4. 栈溢出StackOverflowError错误信息java.lang.StackOverflowError原因方法调用层级太深栈帧数量超过栈最大深度。典型代码public void loop() { loop(); // 无限递归 }业务场景递归没有出口循环调用 A→B→A→B5. 无法创建本地线程错误信息java.lang.OutOfMemoryError: unable to create new native thread原因每个线程都占用栈内存线程数超出系统限制。场景代码里随便new Thread()线程池参数不合理maximumPoolSize 过大高并发下无节制创建线程6. GC 开销超限错误信息java.lang.OutOfMemoryError: GC overhead limit exceeded原因JVM 98% 的时间在做 GC却只回收不到 2% 内存进入 “累死状态”。本质内存快空了又不断产生垃圾。五、内存泄漏 vs 内存溢出这是面试 实战必考点。1. 内存溢出OOM内存真的不够用了一次性加载太多对象加内存可以临时缓解2. 内存泄漏Memory Leak对象不用了但一直被引用GC 无法回收内存慢慢被吃掉最终一定会导致 OOM加内存没用必须改代码最常见泄漏场景static List/Map 不断添加对象ThreadLocal 没remove()数据库连接、IO 流未关闭内部类持有外部类引用缓存不加过期、淘汰机制六、线上 OOM 标准排查流程第一步必须开启 OOM 自动 dump重中之重在 JVM 启动参数里加-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/heap.hprofOOM 时会自动导出内存快照这是定位问题的关键。第二步分析 dump 文件工具Eclipse MAT最强大JProfilerArthas阿里开源线上神器重点看 4 点哪个类的对象最多谁在引用这些对象是不是业务对象是 → 代码问题GC Roots 链在哪里第三步配合命令行工具定位jstat -gc 进程ID 1000 10 # 看GC情况 jmap -heap 进程ID # 看堆配置 jmap -histo 进程ID # 看对象数量 arthas-boot.jar # 阿里Arthas一键排查判断内存泄漏最简单方式每次 Full GC 后内存水位不下降 → 100% 内存泄漏七、OOM 通用解决方案1. 代码层面根治数据库查询必须分页大文件使用流式读取不一次性加载资源用完必须关闭连接、流、ThreadLocal慎用 static 集合线程池标准化不无限创建线程缓存加过期、淘汰、降级策略2. JVM 参数优化-Xms2g -Xmx2g # 堆初始值和最大值 -XX:MetaspaceSize256m -XX:MaxMetaspaceSize256m -XX:HeapDumpOnOutOfMemoryError3. 架构层面消息队列削峰分布式计算压力分散大文件上传走分片热点数据放缓存不全部放内存八、总结一句话记住 OOMOOM 内存满了新对象没地方分配Java heap space最常见大多是没分页、内存泄漏Metaspace溢出 类太多Direct buffer溢出 NIO/Netty 问题StackOverflow 递归 / 调用太深无法创建线程 线程太多排查核心dump 文件 MAT/Arthas90% OOM 都是代码问题不是 JVM 内存太小

更多文章