JVM内存分线程私有(程序计数器、栈、本地方法栈)和共享(堆、元空间)两类,直接内存属NIO堆外区域;五种OOM对应不同区域满溢,加-Xmx仅对Java heap space有效。

系统复习JVM内存架构,关键不是死记区域名称,而是理清“谁用、谁管、谁出错”这三条主线。从线程视角切入,结合生命周期和OOM诱因,记忆会更牢固、排查更有方向。
按线程视角划清私有与共享区域
这是理解整个结构的起点。所有运行时数据区只有两类归属:
- 线程私有:程序计数器、Java虚拟机栈、本地方法栈。每个线程一份,互不干扰,随线程生而生、灭而灭。
- 线程共享:堆(Heap)、元空间(Metaspace,Java 8+取代永久代)。所有线程共用,GC主要作用域,也是OOM高发区。
注意:直接内存不属于JVM规范定义的运行时数据区,但被NIO的DirectByteBuffer频繁使用,同样可能引发OOM,且不受-Xmx控制。
聚焦五个核心区域的核心职责
不用背定义,记住每个区域“干啥活、存啥东西、为啥容易炸”:
立即学习“Java免费学习笔记(深入)”;
- 程序计数器:记录当前线程下一条要执行的字节码指令地址;线程私有、极小、永不OOM。
- Java虚拟机栈:为每个方法调用分配栈帧,存局部变量、操作数栈等;栈深度超限(如递归太深)或单帧过大,会抛StackOverflowError或OutOfMemoryError。
- 堆:所有对象实例和数组的分配地;分新生代(Eden + 两个Survivor)和老年代;GC主战场,-Xms/-Xmx调的就是它。
- 元空间:存放类的元数据(类名、字段、方法、常量池等);使用本地内存(非堆),-XX:MaxMetaspaceSize可限制大小;动态生成大量类(如反射、CGLIB)易触发OOM。
- 直接内存:NIO中DirectByteBuffer背后的真实内存;绕过JVM堆,由System.gc()或Full GC间接回收,泄漏难发现。
结合分代模型理解堆内部流转
堆不是铁板一块,对象在其中是有“成长路径”的:
- 新对象默认分配在Eden区;
- Minor GC后存活对象进入Survivor区(From→To轮换);
- 熬过多次GC(默认15次,由-XX:MaxTenuringThreshold控制)或大对象,直接晋升到老年代;
- 老年代满了触发Major GC或Full GC,停顿时间长,需重点调优。
这个过程把“对象生命周期”和“GC行为”绑在一起,复习时画个简图比纯文字更有效。
带着典型问题反推区域归属
用真实场景激活记忆:
- 报
java.lang.OutOfMemoryError: Java heap space→ 堆不够,查对象泄漏或-Xmx设太小; - 报
java.lang.OutOfMemoryError: Metaspace→ 类加载过多,检查是否频繁动态生成类; - 报
java.lang.StackOverflowError→ 虚拟机栈溢出,大概率是无限递归; - 报
java.lang.OutOfMemoryError: unable to create new native thread→ 线程数超系统限制,本质是本地内存耗尽,和栈空间(-Xss)及总线程数相关。
这样复习,区域不再孤立,而是嵌入到问题诊断链条里,真正能用得上。


















