一、执行引擎的运行基础

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 字节)。
每种数据类型在局部变量表中的占用如下:

数据类型字节大小占用槽位说明
byte1 字节1 槽存储时会扩展为 32 位 int
short2 字节1 槽存储时会扩展为 32 位 int
char2 字节1 槽存储时会扩展为 32 位 int(无符号)
boolean1 字节1 槽实际以 int 形式存储,0=false,非0=true
int4 字节1 槽基本整型类型
float4 字节1 槽单精度浮点数
long8 字节2 槽双槽存储,低位槽存低32位,高位槽存高32位
double8 字节2 槽双槽存储,结构与 long 相同
reference4 或 8 字节(依实现)1 槽存储对象的引用(指针或句柄)
returnAddress4 字节1 槽存储字节码指令的返回地址(用于 jsrret 等指令)

注意:

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 )快速定位。

这种槽位复用机制有效节省了内存空间,并提升了访问效率。


示例:局部变量表的分配

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参数 11整个方法
y参数 22整个方法
a临时变量3整个方法
b临时变量4整个方法
cif 块内变量5if 块作用域内
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]

字节码层面

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)


七、方法调用示例与执行过程

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());
    }
}

编译后的字节码(部分):

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 “一次编译,处处运行” 特性的关键基础。