jvm内存划分为线程私有区(程序计数器、虚拟机栈、本地方法栈)和线程共享区(堆、方法区/元空间、运行时常量池),另含不受规范约束的直接内存;程序计数器不抛oom,栈异常多因递归过深或线程过多,堆oom需分析对象分布,元空间oom常由动态类加载引发,直接内存oom则关联nio资源未释放。

Java 内存区域划分是理解 JVM 行为和定位内存问题的底层基础。不是背下几个名词,而是要清楚每个区域“谁在用、存什么、出错时表现为什么”。排查问题时,错误类型(比如 OutOfMemoryError 或 StackOverflowError)往往直接指向具体区域。
线程私有区:程序计数器、虚拟机栈、本地方法栈
这三个区域生命周期与线程一致,线程结束即释放,不涉及跨线程共享。
-
程序计数器:每个线程独有一块极小空间,只存当前执行的字节码地址;它不会溢出,是 JVM 中唯一不会抛
OutOfMemoryError的区域。 -
虚拟机栈:每次方法调用生成一个栈帧,存放局部变量、操作数栈等。递归过深或单次方法局部变量过多,会触发
StackOverflowError;若线程数过多且栈容量设得太大,可能报OutOfMemoryError: unable to create new native thread。 - 本地方法栈:作用类似虚拟机栈,但服务于 JNI 调用。排查时可先关注是否大量使用了 native 方法(如某些加密库、图像处理 SDK),异常表现与虚拟机栈接近。
线程共享区:堆、方法区(元空间)、运行时常量池
这些区域被所有线程共用,也是 GC 和内存溢出的主战场。
-
Java 堆:对象实例和数组的唯一归属地。GC 主要在这里工作。出现
java.lang.OutOfMemoryError: Java heap space,说明堆内存耗尽且无法扩展——这时要结合堆转储(heap dump)分析对象分布,常见原因包括缓存未清理、大对象长期持有、集合类误用(如static Map不断 put)。 -
方法区 / 元空间:JDK 8+ 使用元空间(Metaspace),直接分配在本地内存。存储类元信息、常量、静态变量等。报
OutOfMemoryError: Metaspace,多因动态类加载(如热部署、Groovy/SpEL 解析、大量代理类生成)未卸载,或-XX:MaxMetaspaceSize设置过小。 -
运行时常量池:属于方法区的一部分,但具备动态性(如
String.intern())。若频繁调用intern且字符串内容不可控,可能撑爆元空间。
容易忽略的非规范区域:直接内存
它不属于 JVM 运行时数据区定义,但实际影响很大。
- 通过
ByteBuffer.allocateDirect()或 NIO 文件映射(MappedByteBuffer)分配,绕过堆,直连操作系统内存。 - 不受
-Xmx控制,但受物理内存和-XX:MaxDirectMemorySize限制。 - 报
OutOfMemoryError: Direct buffer memory时,检查是否 NIO 使用后未调用cleaner或未显式free()(尤其在 Netty 等框架中需注意资源释放逻辑)。
排查思路:从异常信息反推区域
看到 OOM 或栈异常,先看错误消息中的关键词:
-
Java heap space→ 查堆,用jstat -gc观察 GC 频率和回收效果,再用jmap -dump+ MAT 分析对象引用链。 -
Metaspace→ 查类加载数量(jstat -class),确认是否有类泄漏;检查是否启用了 CGLIB/ASM 动态代理且未清理。 -
unable to create new native thread→ 不一定是堆问题,可能是系统级线程数超限(ulimit -u)或 JVM 栈内存(-Xss)设置过大导致线程创建失败。 -
Direct buffer memory→ 检查 NIO 使用模式,确认ByteBuffer是否及时释放,或调整-XX:MaxDirectMemorySize。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











