JVM内存布局核心是按职责与生命周期划分五大区域:堆(线程共享,存对象,OOM主因)、方法区/元空间(存类元数据)、虚拟机栈(线程私有,含栈帧及局部变量表、操作数栈等)、程序计数器(线程私有,指向下条指令)、本地方法栈(服务Native方法);直接内存为堆外但受JVM间接管理。

JVM内存布局的核心在于“按职责划分、按生命周期管理”。五个主要区域不是随意堆砌,而是围绕线程隔离性、数据生命周期和GC协作这三条主线设计的。讲清楚职责,关键不是罗列名词,而是说清“谁用它”“存什么”“怎么管”“出问题时表现为什么”。
堆(Heap):所有对象的统一仓库
堆是JVM中最大、最核心的共享区域,所有通过new创建的对象实例和数组都分配在这里。它不区分线程,所有线程共用同一块堆空间。
- 新生代(Eden + Survivor0/Survivor1):新对象默认落脚地;Minor GC在此发生,存活对象在Survivor区之间复制,年龄达标后晋升老年代
- 老年代(Old Gen):长期存活对象的归宿;Major GC或Full GC主要清理这里
- TLAB(Thread Local Allocation Buffer):每个线程在Eden区内私有的小块缓冲区,避免多线程分配时加锁竞争,提升分配效率
堆内存耗尽会直接抛出java.lang.OutOfMemoryError: Java heap space,这是线上OOM最常见的原因。
方法区(JDK8+为元空间 Metaspace):类的元数据中枢
它存储的是“描述Java类型的数据”,不是对象本身。包括类结构信息(字段、方法、接口)、运行时常量池、静态变量、即时编译后的代码等。
- JDK 7及以前叫“永久代(PermGen)”,受JVM堆内存限制,容易因加载过多类或反射生成类而溢出
- JDK 8起改用“元空间”,使用本地内存(Native Memory),默认无上限,溢出错误变为java.lang.OutOfMemoryError: Metaspace
- 常见诱因:动态代理(如CGLIB)、OSGi容器、大量JSP编译、频繁的类加载/卸载
虚拟机栈(Java Virtual Machine Stack):单线程的方法执行现场
每个线程启动时独享一份,随线程生而生、灭而灭。它的核心单位是“栈帧”,一个方法调用对应一个栈帧入栈,方法返回则栈帧出栈。
- 栈帧内含局部变量表(存基本类型值、对象引用)、操作数栈(临时计算存储)、动态链接(指向运行时常量池中的符号引用)、方法返回地址
- 栈深度超限(如无限递归)→ StackOverflowError
- 线程过多或单个栈过大(-Xss设置过高)→ OutOfMemoryError(无法创建新线程)
程序计数器与本地方法栈:辅助型私有区域
这两个区域体量小、职责明确,常被一起理解:
- 程序计数器:记录当前线程正在执行的字节码指令地址,是线程切换后能恢复执行的关键;唯一不会OOM的区域,线程私有
- 本地方法栈:为JNI调用的Native方法服务,功能与虚拟机栈类似但面向C/C++代码;HotSpot中通常与Java栈合二为一
直接内存(Direct Memory):堆外但受JVM间接管理
它不属于JVM规范定义的运行时数据区,但实际开发中高度相关。NIO的ByteBuffer.allocateDirect()就在直接内存中分配,绕过堆,减少拷贝开销。
- 不受-Xmx限制,但受系统物理内存和-XX:MaxDirectMemorySize参数约束
- 过度使用会导致物理内存不足,引发系统级OOM,或触发Full GC来回收关联的DirectByteBuffer对象
- 典型场景:高性能网络通信(Netty)、大文件传输、零拷贝优化

















