一、执行引擎的运行基础
JVM 执行字节码依赖三大组件:
| 组件 | 作用 |
|---|---|
| 程序计数器(PC Register) | 记录当前线程执行的字节码行号(指令地址)。 |
| Java 栈(Java Stack) | 管理方法的调用与执行,每个方法调用都会创建一个栈帧。 |
| 本地方法栈(Native Method Stack) | 为本地方法(Native Code)执行服务。 |
每个线程都有这三者的独立实例,因此线程之间相互隔离。
这种"线程私有"的设计保证了多线程执行时的安全与独立性。
二、Java 栈与栈帧结构
Java 栈是由一系列栈帧(Stack Frame)组成的。
每当方法被调用时,JVM 会为该方法创建一个新的栈帧并压入 Java 栈;
方法执行完毕后,栈帧出栈,控制权回到上一个方法。
一个栈帧主要包含以下三部分内容:

这三者共同构成方法执行时的运行时的上下文。
三、局部变量表(Local Variable Table)
局部变量表是每个栈帧中非常核心的结构,用于存储:
方法的参数(包括基本类型与引用类型)
方法体中定义的局部变量
实例方法中隐含的 this 引用
JVM 通过 索引(Index) 访问局部变量表中的元素,例如:
iload_0 // 加载索引 0 对应的变量
istore_2 // 将栈顶值存入索引 2 的变量槽
存储规则与数据类型
局部变量表以 槽(Slot) 为最小存储单位,每个槽大小为 32 位(4 字节)。
每种数据类型在局部变量表中的占用如下:
| 数据类型 | 字节大小 | 占用槽位 | 说明 |
|---|---|---|---|
| byte | 1 字节 | 1 槽 | 存储时会扩展为 32 位 int |
| short | 2 字节 | 1 槽 | 存储时会扩展为 32 位 int |
| char | 2 字节 | 1 槽 | 存储时会扩展为 32 位 int(无符号) |
| boolean | 1 字节 | 1 槽 | 实际以 int 形式存储,0=false,非0=true |
| int | 4 字节 | 1 槽 | 基本整型类型 |
| float | 4 字节 | 1 槽 | 单精度浮点数 |
| long | 8 字节 | 2 槽 | 双槽存储,低位槽存低32位,高位槽存高32位 |
| double | 8 字节 | 2 槽 | 双槽存储,结构与 long 相同 |
| reference | 4 或 8 字节(依实现) | 1 槽 | 存储对象的引用(指针或句柄) |
| returnAddress | 4 字节 | 1 槽 | 存储字节码指令的返回地址(用于 jsr、ret 等指令) |
注意:
byte、short、char、boolean 在加载进局部变量表时都会被自动扩展为 int 类型;
long 和 double 是 JVM 中唯一占用两个槽(Slot)的类型;
reference 在不同 JVM 实现中占用的实际字节数可能不同(32 位或 64 位),但始终只占用 1 个槽;
returnAddress 是为旧版 jsr/ret(子例程调用)指令保留的,现代编译器几乎不再使用。
槽位分配与复用规则
**1.索引 0:**若方法为实例方法,则该槽存放 this 引用;
**2.从索引 1 开始:**依次存放方法参数(按声明顺序);
**3.参数之后:**为局部变量分配槽位;
**4.槽位复用:**当变量超出作用域后,槽位可被后续变量复用;
5.索引号递增访问:JVM 通过索引(如 iload_1、atore_2 )快速定位。
这种槽位复用机制有效节省了内存空间,并提升了访问效率。
示例:局部变量表的分配
java
void test(int x, int y) {
int a = x + y;
int b = x - y;
if (a > b) {
int c = a - b;
}
int d = a + b;
}
编译后的局部变量分配情况如下:
| 变量名 | 说明 | 槽位 (Slot) | 生命周期 |
|---|---|---|---|
| this | 当前实例引用 | 0 | 整个方法 |
| x | 参数 1 | 1 | 整个方法 |
| y | 参数 2 | 2 | 整个方法 |
| a | 临时变量 | 3 | 整个方法 |
| b | 临时变量 | 4 | 整个方法 |
| c | if 块内变量 | 5 | if 块作用域内 |
| d | 临时变量 | 5(复用 c 的槽) | if 块之后 |
当 if 语句块结束后,变量 c 的作用域结束,槽位 5 可被 d 复用。
这体现了 JVM 的局部变量槽复用机制,是字节码层面节省内存空间的常见优化。
四、操作数栈(Operand Stack)
操作数栈是一个先进后出(LIFO)的栈结构,用于执行字节码指令中的运算操作。
以1 + 2 为例:
| 步骤 | 操作 | 操作数栈状态 |
|---|---|---|
| 1 | 将常量 1 压栈 | [1] |
| 2 | 将常量 2 压栈 | [1, 2] |
| 3 | 执行 iadd | 弹出 1、2,相加后压回结果 [3] |
字节码层面 :
java
iconst_1 // 压入常量1
iconst_2 // 压入常量2
iadd // 弹出两个整数相加,再压回结果
在执行过程中:
iload 从局部变量表加载变量到栈顶;
istore 将栈顶值保存回局部变量表;
所有计算都以"入栈 → 运算 → 出栈 → 再入栈"的方式进行。
五、帧数据区(Frame Data Area)
帧数据区保存着方法执行时的上下文信息,包括:
1. 动态连接(Dynamic Linking)
每个栈帧中都有一个指向当前类运行时常量池(Runtime Constant Pool) 的引用。
常量池中保存着类、字段、方法等的符号引用。
类加载阶段解析的称为静态解析(Static Resolution);
方法调用阶段解析的称为动态连接(Dynamic Linking)。
2. 返回地址(Return Address)
当方法正常执行完毕后,JVM 会根据返回地址跳回主调方法继续执行。
3. 异常表(Exception Table)
编译器在编译方法时会生成异常表,用于描述异常时的跳转逻辑。
若异常未被捕获,当前栈帧被销毁,异常沿调用栈向上抛出,形成异常链(Exception Chain)。
六、栈帧重叠优化(Stack Frame Overlap)
为了提升性能,JVM 通常将局部变量表与操作数栈分配在一块连续内存中,并让主调方法与被调方法之间的内存部分重叠。
这样可以:
减少参数传递的内存拷贝;
缩短方法调用的准备时间;
提高整体执行性能。
这种机制称为 栈帧重叠(Stack Frame Overlap)。
七、方法调用示例与执行过程
java
public class Compute {
public int add(int a, int b) {
int sum = a + b;
return sum;
}
public int calculate() {
int x = 10;
int y = 20;
int result = add(x, y);
result = result * 2;
return result;
}
public static void main(String[] args) {
Compute c = new Compute();
System.out.println(c.calculate());
}
}
编译后的字节码(部分):
java
public int calculate();
0: bipush 10 // 压入常量 10
2: istore_1 // 存入局部变量表 slot1 -> x
3: bipush 20 // 压入常量 20
5: istore_2 // 存入 slot2 -> y
6: aload_0 // 加载 this
7: iload_1 // 加载 x
8: iload_2 // 加载 y
9: invokevirtual #2 // 调用 add(int,int)
12: istore_3 // 存储 add 返回值 -> result
13: iload_3 // 加载 result
14: iconst_2 // 压入常量 2
15: imul // 相乘
16: istore_3 // 存回 result
17: iload_3 // 加载 result
18: ireturn // 返回结果
此过程体现了 局部变量表 ↔ 操作数栈 的频繁数据交换,是字节码执行的核心逻辑。
栈帧与执行过程图

八、JVM 的栈式架构特性
JVM 字节码是一种基于栈的指令集架构(Stack-Based ISA),所有计算都通过操作数栈完成。
| 特点 | 说明 |
|---|---|
| 结构简洁 | 指令集通用,操作只需面向栈顶元素 |
| 跨平台性强 | 不依赖底层寄存器或硬件架构 |
| 执行效率相对较低 | 指令数量多,依赖频繁入栈出栈 |
| 性能优化手段 | JIT(即时编译)可将热点字节码编译为本地机器码 |
九、整体执行过程总结
| 组成部分 | 作用 | 特点 |
|---|---|---|
| 程序计数器 | 记录当前执行指令地址 | 每线程独立 |
| Java 栈 | 管理方法调用 | 栈帧入栈 / 出栈 |
| 局部变量表 | 存储参数和局部变量 | 槽位可复用 |
| 操作数栈 | 存放中间计算结果 | LIFO 结构 |
| 帧数据区 | 保存上下文信息 | 含动态连接 / 异常表 |
| 栈帧重叠 | 优化调用性能 | 节省内存、减少拷贝 |
十、总结
JVM 执行引擎以线程私有的栈结构为核心,通过程序计数器精确定位字节码指令,依靠局部变量表与操作数栈完成数据存储与运算处理,并借助帧数据区维护方法上下文信息,同时利用栈帧重叠机制优化调用性能。整个执行体系以栈为中心,结构简洁、跨平台性强、可优化性高,是实现 Java "一次编译,处处运行" 特性的关键基础。