JVM 内存模型
2026/1/15大约 4 分钟
JVM 内存模型
运行时数据区
程序计数器
- 作用:记录当前线程执行的字节码行号
- 特点:
- 线程私有
- 唯一不会发生 OOM 的区域
- 执行 Native 方法时为空
虚拟机栈
每个方法执行时创建一个栈帧(Stack Frame)。
栈帧结构
局部变量表
存储方法参数和局部变量,以 Slot 为单位。
public void method(int a, long b) {
int c = 10;
Object obj = new Object();
}
// Slot 分配:
// Slot 0: this(实例方法)
// Slot 1: a (int, 1个Slot)
// Slot 2-3: b (long, 2个Slot)
// Slot 4: c (int, 1个Slot)
// Slot 5: obj (reference, 1个Slot)操作数栈
执行字节码指令时的工作区。
int a = 1;
int b = 2;
int c = a + b;
// 字节码执行过程:
// iconst_1 -> 操作数栈: [1]
// istore_1 -> 操作数栈: [], 局部变量表: a=1
// iconst_2 -> 操作数栈: [2]
// istore_2 -> 操作数栈: [], 局部变量表: a=1, b=2
// iload_1 -> 操作数栈: [1]
// iload_2 -> 操作数栈: [1, 2]
// iadd -> 操作数栈: [3]
// istore_3 -> 操作数栈: [], 局部变量表: a=1, b=2, c=3栈溢出
// StackOverflowError:栈深度超过限制
public void recursion() {
recursion(); // 无限递归
}
// 调整栈大小:-Xss256k本地方法栈
为 Native 方法服务,与虚拟机栈类似。
堆
存储对象实例,是 GC 的主要区域。
堆内存结构
内存分配
新生代 : 老年代 = 1 : 2(默认)
Eden : S0 : S1 = 8 : 1 : 1(默认)对象分配流程
堆内存参数
-Xms512m # 初始堆大小
-Xmx1024m # 最大堆大小
-Xmn256m # 新生代大小
-XX:SurvivorRatio=8 # Eden:Survivor 比例
-XX:NewRatio=2 # 新生代:老年代 比例
-XX:MaxTenuringThreshold=15 # 晋升老年代的年龄阈值方法区
存储类信息、常量、静态变量、即时编译器编译后的代码。
演变历史
| JDK 版本 | 实现方式 |
|---|---|
| JDK 7 及之前 | 永久代(PermGen) |
| JDK 8 及之后 | 元空间(Metaspace) |
永久代 vs 元空间
| 特性 | 永久代 | 元空间 |
|---|---|---|
| 存储位置 | JVM 内存 | 本地内存 |
| 大小限制 | 固定大小 | 默认无上限 |
| GC | Full GC 时回收 | 达到阈值时回收 |
| OOM | PermGen space | Metaspace |
元空间参数
-XX:MetaspaceSize=128m # 初始大小
-XX:MaxMetaspaceSize=256m # 最大大小运行时常量池
方法区的一部分,存储编译期生成的字面量和符号引用。
字符串常量池
String s1 = "hello"; // 常量池
String s2 = "hello"; // 常量池(同一个)
String s3 = new String("hello"); // 堆
String s4 = s3.intern(); // 常量池
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1 == s4); // true字符串常量池位置
| JDK 版本 | 位置 |
|---|---|
| JDK 6 | 永久代 |
| JDK 7+ | 堆 |
直接内存
不属于 JVM 运行时数据区,但被频繁使用。
// NIO 使用直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
// 参数设置
// -XX:MaxDirectMemorySize=256m对象的创建过程
内存分配方式
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 指针碰撞 | 移动指针分配连续内存 | 内存规整(Serial、ParNew) |
| 空闲列表 | 从列表中找到足够大的空间 | 内存不规整(CMS) |
线程安全问题
- CAS + 失败重试:保证原子性
- TLAB:每个线程预先分配一块内存
-XX:+UseTLAB # 启用 TLAB(默认开启)
-XX:TLABSize=512k # TLAB 大小对象的内存布局
+------------------+
| 对象头 | Mark Word + 类型指针
+------------------+
| 实例数据 | 字段内容
+------------------+
| 对齐填充 | 保证 8 字节对齐
+------------------+Mark Word(64位)
| 锁状态 | 内容 |
|---|---|
| 无锁 | hashCode、GC 年龄、偏向锁标志 |
| 偏向锁 | 线程 ID、Epoch、GC 年龄 |
| 轻量级锁 | 指向栈中锁记录的指针 |
| 重量级锁 | 指向 Monitor 的指针 |
| GC 标记 | 空 |
对象的访问定位
句柄访问
栈 -> 句柄池 -> 对象实例数据
-> 对象类型数据直接指针(HotSpot 使用)
栈 -> 对象实例数据 -> 对象类型数据| 方式 | 优点 | 缺点 |
|---|---|---|
| 句柄 | 对象移动时只需修改句柄 | 多一次间接访问 |
| 直接指针 | 访问速度快 | 对象移动时需修改引用 |