OutOfMemoryError不是单一错误,而是JVM不同内存区域资源枯竭的多种表现;最常见的是堆内存溢出(Java heap space),主因是活对象占满堆且GC无法回收,典型场景包括内存泄漏、大对象误用和缓存失控;元空间耗尽源于动态类加载过多;直接内存不足由ByteBuffer未释放或限制过小导致;线程创建失败则因系统级资源耗尽而非栈深度问题。

OutOfMemoryError 不是单一错误,而是 JVM 在不同内存区域资源枯竭时发出的“求救信号”。搞不清具体是哪块内存告急,调参、加机器、改代码都可能南辕北辙。
堆内存溢出(Java heap space)
这是生产环境最常遇到的类型,本质是新对象申请内存时,堆里活对象太多、GC 清不掉、空间不够分。
- 内存泄漏:静态集合(如 static Map/List)不断 add 却不 remove;ThreadLocal 使用后没调 remove;监听器注册后未反注册
- 大对象误用:一次性读取 GB 级文件到 byte[];递归生成深层树结构;全量查库塞进 ArrayList
- 缓存失控:本地缓存没设上限、没配过期策略,数据越积越多
元空间耗尽(Metaspace)
JDK 8+ 中,类的元数据(类名、方法、字段等)不再放堆里,而是存在本地内存中。这里爆了,和堆大小无关,只和类数量、系统内存有关。
- 动态代理泛滥:Spring AOP + CGLIB 大量生成代理类;微服务网关加载数百个 jar 包
- 热部署频繁:开发阶段反复 reload 应用,ClassLoader 加载新版本类却未卸载旧类
- 反射大量使用:运行时通过 Class.forName() 或 Method.invoke() 触发大量类加载
直接内存不足(Direct buffer memory)
通过 ByteBuffer.allocateDirect() 分配的堆外内存,不受 -Xmx 控制,但受 -XX:MaxDirectMemorySize 限制(默认等于 -Xmx)。
- NIO 场景常见:Netty 通信、NIO 文件通道、PostgreSQL 驱动底层使用直接内存
- 释放遗漏:allocateDirect 后未显式 clean(),或依赖 System.gc() 这种不可靠机制
- 容器限制下更易触发:K8s 中 Pod 内存 limit 设得紧,直接内存叠加堆内存容易超限
线程创建失败(unable to create new native thread)
这不是栈空间不够,而是操作系统层面已无法为新线程分配资源——可能是线程数超限,也可能是每个线程栈太大导致总内存被瓜分完。
- 线程池无界:Executors.newCachedThreadPool() 在高并发下无限建线程
- -Xss 设置过大:比如设成 4m,1000 个线程就占 4GB 栈内存
- 递归或深度调用链:虽单线程不报 OOM,但持续创建新线程来处理会压垮系统

















