OutOfMemoryError需据错误后缀定位内存区域:Java heap space→堆内存泄漏;Metaspace→类加载器泄漏;unable to create new native thread→系统线程数超限;Direct buffer memory→堆外内存泄漏。

OutOfMemoryError发生时,先看错误信息里的关键词
Java抛出OutOfMemoryError不是单一问题,而是几类完全不同的内存耗尽场景。错误信息末尾的括号内容才是关键线索,比如:java.lang.OutOfMemoryError: Java heap space、java.lang.OutOfMemoryError: Metaspace、java.lang.OutOfMemoryError: unable to create new native thread。不同后缀对应不同内存区域,排查路径完全不同。
常见错误类型与对应区域:
-
Java heap space→ 堆内存不足(对象太多或过大) -
Metaspace→ 类元数据区溢出(动态生成类多,如大量使用CGLIB、反射、热部署) -
unable to create new native thread→ 线程数超操作系统限制(不是堆不够,是JVM进程无法再fork线程) -
Direct buffer memory→ByteBuffer.allocateDirect()分配的堆外内存耗尽(未显式调用cleaner或未关闭通道)
jstat和jmap配合定位堆内存泄漏点
如果错误是Java heap space,优先用jstat确认是否真的长期增长,而不是偶发GC失败。运行jstat -gc 每2秒输出一次GC统计,重点观察OU(老年代已使用)是否持续上升且Full GC后不下降。
确认存在泄漏后,用jmap抓堆快照分析:
立即学习“Java免费学习笔记(深入)”;
- 先尝试轻量级命令:
jmap -histo:live,查看存活对象数量和总大小,快速识别异常多的类(如byte[]、HashMap$Node、自定义缓存类) - 若需深度分析,执行
jmap -dump:format=b,file=heap.hprof,再用VisualVM或Eclipse MAT打开。注意:该操作会触发Full GC,生产环境慎用;若JVM启用了-XX:+HeapDumpBeforeFullGC,可直接检查最近生成的.hprof文件 - 避免用
jmap -dump:live在高负载服务上——它会暂停所有应用线程,可能引发雪崩
Metaspace溢出不能靠加大-XX:MaxMetaspaceSize硬扛
Metaspace错误往往源于类加载器泄漏,而非参数设小了。单纯增大-XX:MaxMetaspaceSize只是延迟问题爆发时间,甚至掩盖真实泄漏点。
排查步骤:
- 用
jstat -gcmetacapacity查当前元空间容量使用情况;用jstat -gccause确认是否频繁发生Metadata GC - 检查是否有重复部署、热加载框架(如Spring Boot DevTools、JRebel)、或手动
URLClassLoader未释放——每次加载新类,旧类加载器若被引用,其加载的所有类元数据都无法回收 - 开启类加载日志:
-XX:+TraceClassLoading和-XX:+TraceClassUnloading,观察哪些类反复加载却从不卸载 - 用
jcmd辅助判断是否原生内存(如JIT编译、JNI)也在增长VM.native_memory summary
native thread报错要查系统级限制,不是JVM参数
unable to create new native thread本质是操作系统拒绝为JVM进程创建新线程,和-Xmx或堆大小无关。根本原因通常是:
- Linux默认每个进程线程数上限由
/proc/中/limits Max processes(即RLIMIT_NPROC)控制,常为1024或4096 - JVM线程栈过大(
-Xss设太高),导致同样线程数下更快耗尽虚拟内存 - 容器环境未正确配置
ulimit,或Kubernetes中securityContext.runAsUser导致用户级进程数限制生效
验证方式:用ps -T -p 查当前JVM线程总数;用cat /proc/查上限。调整需在启动JVM前设置ulimit -u,而非修改JVM参数。
堆外内存(Direct buffer memory)同理:它不受-Xmx约束,但受-XX:MaxDirectMemorySize限制(默认等于-Xmx)。泄漏常发生在NIO通道未关闭、NettyPooledByteBufAllocator配置不当、或忘记调用Buffer.clear()/cleaner.clean()。
真正难的是区分「内存增长」和「内存泄漏」——前者可能只是业务量上升,后者是对象本该回收却一直被强引用挂着。别急着加内存,先看引用链。










