JVM字节码指令集参考
约 1970 字大约 7 分钟
javajvmbytecode
2025-03-09
概述
JVM字节码指令由一个字节的操作码(opcode, 0x00~0xFF)和零到多个操作数组成。JVM是基于栈的虚拟机,大部分指令都从操作数栈(Operand Stack)取值,执行后将结果压回栈中。
JVM执行模型
加载与存储指令
将数据在局部变量表和操作数栈之间传输。
加载指令(局部变量表 → 操作数栈)
| 指令 | 操作 | 说明 |
|---|---|---|
iload / iload_0~iload_3 | 加载int | _0~_3是简写形式 |
lload | 加载long | 占2个栈槽位 |
fload | 加载float | |
dload | 加载double | 占2个栈槽位 |
aload / aload_0~aload_3 | 加载引用 | aload_0常用于加载this |
存储指令(操作数栈 → 局部变量表)
| 指令 | 操作 |
|---|---|
istore / istore_0~istore_3 | 存储int |
lstore | 存储long |
fstore | 存储float |
dstore | 存储double |
astore | 存储引用 |
常量加载指令
| 指令 | 操作 | 范围 |
|---|---|---|
iconst_m1~iconst_5 | 加载int常量 | -1 ~ 5 |
lconst_0, lconst_1 | 加载long常量 | 0, 1 |
fconst_0~fconst_2 | 加载float常量 | 0.0, 1.0, 2.0 |
dconst_0, dconst_1 | 加载double常量 | 0.0, 1.0 |
aconst_null | 加载null | |
bipush | 加载byte值 | -128 ~ 127 |
sipush | 加载short值 | -32768 ~ 32767 |
ldc | 从常量池加载 | int/float/String/Class |
ldc_w | 宽索引版本 | |
ldc2_w | 从常量池加载 | long/double |
// 示例
int a = 3; // iconst_3; istore_1
int b = 200; // sipush 200; istore_2
int c = 100000; // ldc #N; istore_3算术指令
| 操作 | int | long | float | double |
|---|---|---|---|---|
| 加法 | iadd | ladd | fadd | dadd |
| 减法 | isub | lsub | fsub | dsub |
| 乘法 | imul | lmul | fmul | dmul |
| 除法 | idiv | ldiv | fdiv | ddiv |
| 取余 | irem | lrem | frem | drem |
| 取负 | ineg | lneg | fneg | dneg |
| 位移 | ishl/ishr/iushr | lshl/lshr/lushr | — | — |
| 按位或 | ior | lor | — | — |
| 按位与 | iand | land | — | — |
| 按位异或 | ixor | lxor | — | — |
| 自增 | iinc | — | — | — |
// int a = 10 + 20;
bipush 10 // 栈: [10]
bipush 20 // 栈: [10, 20]
iadd // 栈: [30]
istore_1 // a = 30, 栈: []
// i++ 在循环中
iinc 1, 1 // 局部变量表slot 1直接加1(不经过操作数栈)类型转换指令
宽化转换(int→long/float/double)自动进行,不会丢失信息。窄化转换需要显式类型转换,可能丢失精度。
int i = 256;
byte b = (byte) i; // i2b → 结果为0(截断高位)对象创建与操作
// new Object()
new #2 // 分配内存,将引用压入栈
dup // 复制引用(一份用于构造,一份留在栈中)
invokespecial #1 // 调用<init>构造方法
// 字段访问
getfield #7 // 获取实例字段 → 弹出objectref,压入value
putfield #7 // 设置实例字段 → 弹出objectref和value
getstatic #12 // 获取静态字段
putstatic #12 // 设置静态字段
// 数组
newarray 10 // 创建基本类型数组(10=int)
anewarray #8 // 创建引用类型数组
multianewarray #8, 2 // 创建多维数组
arraylength // 获取数组长度
iaload / iastore // 数组元素读写(int类型)
aaload / aastore // 数组元素读写(引用类型)
// 类型检查
instanceof #8 // 检查是否是指定类型,压入0或1
checkcast #8 // 类型强转,失败抛ClassCastException操作数栈管理
| 指令 | 操作 |
|---|---|
pop | 弹出栈顶1个值(非long/double) |
pop2 | 弹出栈顶2个值或1个long/double |
dup | 复制栈顶值并压入 |
dup_x1 | 复制栈顶值并插入到栈顶第二个值之下 |
dup_x2 | 复制栈顶值并插入到栈顶第三个值之下 |
dup2 | 复制栈顶两个值 |
swap | 交换栈顶两个值 |
// dup_x1 示例(用于 a.field = ++count 模式)
// 栈: [objectref, value]
dup_x1
// 栈: [value, objectref, value]
// 后续 putfield 消耗 objectref+value,栈中还剩一个value作为表达式结果控制转移指令
条件跳转
| 指令 | 条件 |
|---|---|
ifeq | == 0 |
ifne | != 0 |
iflt | < 0 |
ifge | >= 0 |
ifgt | > 0 |
ifle | <= 0 |
if_icmpeq | 两个int相等 |
if_icmpne | 两个int不等 |
if_acmpeq | 两个引用相同 |
ifnull | == null |
ifnonnull | != null |
其他控制
| 指令 | 操作 |
|---|---|
goto | 无条件跳转 |
tableswitch | 连续case值的switch |
lookupswitch | 非连续case值的switch |
athrow | 抛出异常 |
ireturn/lreturn/freturn/dreturn/areturn | 返回值 |
return | void返回 |
// switch-case 编译
// 连续case → tableswitch(跳转表,O(1))
switch (x) {
case 1: ... break;
case 2: ... break;
case 3: ... break;
}
// tableswitch 1 to 3
// 1: offset1
// 2: offset2
// 3: offset3
// default: offsetD
// 稀疏case → lookupswitch(二分查找,O(log n))
switch (x) {
case 10: ... break;
case 100: ... break;
case 1000: ... break;
}
// lookupswitch
// 10: offset1
// 100: offset2
// 1000: offset3
// default: offsetD方法调用指令
| 指令 | 用途 | 分派机制 |
|---|---|---|
invokevirtual | 实例方法(普通方法) | 虚方法表(vtable)动态分派 |
invokespecial | <init>、super.method()、private 方法 | 静态绑定 |
invokestatic | 静态方法 | 静态绑定 |
invokeinterface | 接口方法 | itable动态分派 |
invokedynamic | lambda、字符串拼接等 | 引导方法(bootstrap method)动态绑定 |
// invokedynamic示例 — lambda表达式
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(System.out::println);
// 编译后:
// invokedynamic #N <accept, BootstrapMethods #0>
// BootstrapMethods中指向LambdaMetafactory.metafactoryinvokedynamic详解
同步指令
// monitorenter / monitorexit
synchronized (obj) {
// 临界区
}
// 编译后:
aload_1
monitorenter // 获取obj的monitor
// ... 临界区代码 ...
aload_1
monitorexit // 释放monitor
goto end
// 异常处理
aload_1
monitorexit // 异常时也释放monitor
athrow
end:完整示例分析
public int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
} public int fibonacci(int);
Code:
0: iload_1 // 加载参数n
1: iconst_1 // 加载常量1
2: if_icmpgt 7 // if (n > 1) goto 7
5: iload_1 // 加载n
6: ireturn // return n
7: aload_0 // 加载this
8: iload_1 // 加载n
9: iconst_1 // 加载1
10: isub // n - 1
11: invokevirtual #2 // this.fibonacci(n-1)
14: aload_0 // 加载this
15: iload_1 // 加载n
16: iconst_2 // 加载2
17: isub // n - 2
18: invokevirtual #2 // this.fibonacci(n-2)
21: iadd // 两个结果相加
22: ireturn // 返回结果常见FAQ
Q: JVM为什么选择基于栈而非基于寄存器?
A: 基于栈的指令集更紧凑(操作数隐含在栈中,不需要指定寄存器编号),实现更简单,且与硬件架构无关,有利于跨平台。缺点是同样的逻辑需要更多指令(更多的入栈出栈操作)。Android的Dalvik/ART选择了基于寄存器的设计。
Q: byte/short/char/boolean在字节码层面如何处理?
A: JVM在字节码层面不直接支持byte/short/char/boolean的算术运算。这些类型在操作数栈上都按int处理(宽化),只在存储回数组或字段时进行窄化(如i2b, i2s)。
Q: 如何查看字节码?
A: javap -c -v ClassName.class 是最基本的方式。IDE插件如 IntelliJ 的 "jclasslib" 提供图形化查看。ASM Bytecode Viewer 可以看到ASM风格的字节码。
总结
JVM字节码是Java代码和JVM之间的桥梁。掌握常用字节码指令,能帮助理解Java代码的真实执行逻辑、编译器优化行为(如自动装箱、字符串拼接、lambda实现),以及在字节码增强(AOP、Agent、动态代理)场景中进行有效开发。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于