最常见的oom是java heap space,即堆内存溢出,主因是活对象占满堆空间且gc无法腾出足够内存,典型场景包括全表加载、缓存无界、大文件误转字节数组等。

Java应用崩溃时最常见的致命错误就是 OutOfMemoryError(OOM),但它不是单一问题,而是JVM不同内存区域“告急”的多种表现。识别具体是哪一类OOM,直接决定排查方向是否正确——搞错区域,调参、改代码都白忙。
堆内存溢出:java.lang.OutOfMemoryError: Java heap space
这是生产环境最常遇到的OOM类型。根本原因只有一个:JVM堆里活对象占满空间,GC反复清理也腾不出足够内存给新对象分配。
- 典型诱因包括:一次性加载全表数据到List、缓存未设上限且长期累积、大文件流式读取时误转为byte[]、递归生成树结构未控制深度
- 关键区分点在于判断是内存泄漏还是内存溢出:前者是本该回收的对象因引用链残留而无法释放;后者是对象确实都要用,只是堆配小了或设计不合理
- 必须配合
-XX:+HeapDumpOnOutOfMemoryError参数开启堆快照,再用 MAT 或 VisualVM 分析谁在“囤货”、谁挡着GC Roots
栈内存耗尽:StackOverflowError 与 OOM 的边界
HotSpot 虚拟机把虚拟机栈和本地方法栈合二为一,由 -Xss 控制单线程栈大小。这里存在两种完全不同的失败路径:
- StackOverflowError:单线程递归过深或局部变量过多,触发栈帧压栈超限。现象是报错快、堆栈trace极长,但不产生dump
- OutOfMemoryError: unable to create native thread:不是栈不够用,而是系统已无法为新线程分配栈内存。常见于线程池无限制创建、异步任务泛滥、-Xss设得过大导致总内存被线程瓜分殆尽
元空间溢出:java.lang.OutOfMemoryError: Metaspace
JDK 8 后永久代被元空间取代,类元数据(类名、字段、方法字节码等)存在本地内存中。这类OOM不再受堆大小影响,而是受限于操作系统可用内存和 -XX:MaxMetaspaceSize。
- 高发场景:频繁动态生成类(如使用CGLIB代理大量接口、热部署框架反复加载卸载)、大量使用反射、微服务网关级联加载数百个jar包
- 注意:默认情况下元空间无上限,容易吃光系统内存。应显式设置
-XX:MaxMetaspaceSize=256m类似值,并监控java.lang:type=MemoryPool,name=MetaspaceMBean指标
直接内存溢出:java.lang.OutOfMemoryError: Direct buffer memory
通过 ByteBuffer.allocateDirect() 分配的堆外内存,不受-Xmx控制,由 -XX:MaxDirectMemorySize 约束(默认等于-Xmx)。NIO、Netty、RocketMQ底层大量使用。
- 典型根因:Netty中未及时
release()PooledByteBuf、自定义NIO客户端未关闭Channel导致DirectBuffer堆积、JDK Bug(如某些版本FileChannel.map()未释放) - 排查需结合
-XX:+PrintGCDetails查看G1或ZGC是否报告DirectMemory相关统计,或用 Native Memory Tracking(-XX:NativeMemoryTracking=detail)定位











