JVM 执行引擎:字节码是如何被执行的

一、执行引擎的运行基础

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 槽 存储字节码指令的返回地址(用于 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 )快速定位。

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


示例:局部变量表的分配

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

相关推荐
无敌最俊朗@16 小时前
SQLite 约束 (Constraints) 面试核心知识点
java·开发语言·jvm
milanyangbo20 小时前
谁生?谁死?从引用计数到可达性分析,洞悉GC的决策逻辑
java·服务器·开发语言·jvm·后端·算法·架构
m0_7482313120 小时前
深入JVM:让Java性能起飞的核心原理与优化策略
java·开发语言·jvm
Vaclee21 小时前
JVM超详解
开发语言·jvm
lqj_本人1 天前
【Rust编程:从小白入坑】Rust所有权系统
开发语言·jvm·rust
摇滚侠2 天前
Spring Boot3零基础教程,JVM 编译原理,笔记87
jvm·spring boot·笔记
虾..2 天前
C++ 异常
jvm
踩坑小念2 天前
进程 线程 协程基本概念和区别 还有内在联系
java·linux·jvm·操作系统
学习编程的Kitty2 天前
JavaEE初阶——多线程(4)线程安全
java·开发语言·jvm