加载存储指令(局部变量/常量入栈)
数据搬运工:加载存储指令
如果说 JVM 是一个工厂,那加载存储指令就是工厂里的「搬运工」。它们负责在操作数栈和局部变量表之间来回搬运数据。
所有计算都发生在操作数栈上,所以这些搬运指令是字节码中最频繁出现的。
局部变量表速查
局部变量表的槽位分配规则:
java
public class Demo {
public void method(long a, double b, Object c, int d) {
// a → slot 0, 1 (long 占两个槽)
// b → slot 2, 3 (double 占两个槽)
// c → slot 4 (对象引用)
// d → slot 5 (int)
// this → slot 6 (非静态方法才有)
}
}| 类型 | 槽数 |
|---|---|
int、float、reference | 1 槽 |
long、double | 2 槽 |
常量入栈指令
iconst 系列(-1 ~ 5)
iconst_m1 // int -1 入栈 opcode: 0x02
iconst_0 // int 0 入栈 opcode: 0x03
iconst_1 // int 1 入栈 opcode: 0x04
iconst_2 // int 2 入栈 opcode: 0x05
iconst_3 // int 3 入栈 opcode: 0x06
iconst_4 // int 4 入栈 opcode: 0x07
iconst_5 // int 5 入栈 opcode: 0x08lconst 系列(0 ~ 1)
lconst_0 // long 0 入栈 opcode: 0x09
lconst_1 // long 1 入栈 opcode: 0x0Afconst 系列(0 ~ 2)
fconst_0 // float 0.0f 入栈 opcode: 0x0B
fconst_1 // float 1.0f 入栈 opcode: 0x0C
fconst_2 // float 2.0f 入栈 opcode: 0x0Ddconst 系列(0 ~ 1)
dconst_0 // double 0.0 入栈 opcode: 0x0E
dconst_1 // double 1.0 入栈 opcode: 0x0Faconst_null
aconst_null // null 引用入栈 opcode: 0x01bipush 和 sipush
当常量超出 [-1, 5] 的范围时:
bipush 127 // byte 常量入栈 (-128~127) opcode: 0x10
sipush 32767 // short 常量入栈 (-32768~32767) opcode: 0x11ldc:从常量池加载
更大的常量需要从常量池加载:
ldc #3 // 从常量池加载 #3(int/float/string)
ldc_w #255 // 宽化版本,支持更大的索引
ldc2_w #1 // 从常量池加载 long/doublejava
// 示例
String s = "Hello World"; // 字符串太长,不能用 bipush
// 字节码:ldc #3 // 从常量池加载 "Hello World"局部变量加载指令
iload 系列
iload // 从槽 n 加载 int opcode: 0x15
iload_0 // 从槽 0 加载 int opcode: 0x1A
iload_1 // 从槽 1 加载 int opcode: 0x1B
iload_2 // 从槽 2 加载 int opcode: 0x1C
iload_3 // 从槽 3 加载 int opcode: 0x1Dlload 系列
lload // 从槽 n 加载 long
lload_0 // 从槽 0 加载 long opcode: 0x1E
lload_1 // 从槽 1 加载 long opcode: 0x1F
lload_2 // 从槽 2 加载 long opcode: 0x20
lload_3 // 从槽 3 加载 long opcode: 0x21fload 系列
fload // 从槽 n 加载 float
fload_0 // 从槽 0 加载 float opcode: 0x22
fload_1 // 从槽 1 加载 float opcode: 0x23
fload_2 // 从槽 2 加载 float opcode: 0x24
fload_3 // 从槽 3 加载 float opcode: 0x25dload 系列
dload // 从槽 n 加载 double
dload_0 // 从槽 0 加载 double opcode: 0x26
dload_1 // 从槽 1 加载 double opcode: 0x27
dload_2 // 从槽 2 加载 double opcode: 0x28
dload_3 // 从槽 3 加载 double opcode: 0x29aload 系列
aload // 从槽 n 加载 reference
aload_0 // 从槽 0 加载引用 opcode: 0x2A
aload_1 // 从槽 1 加载引用 opcode: 0x2B
aload_2 // 从槽 2 加载引用 opcode: 0x2C
aload_3 // 从槽 3 加载引用 opcode: 0x2D存储指令(栈 → 局部变量)
存储指令把操作数栈顶的值弹出,放入局部变量表:
istore // 栈顶 int → 槽 n opcode: 0x36
istore_0 // 栈顶 int → 槽 0 opcode: 0x3B
istore_1 // 栈顶 int → 槽 1 opcode: 0x3C
istore_2 // 栈顶 int → 槽 2 opcode: 0x3D
istore_3 // 栈顶 int → 槽 3 opcode: 0x3E
lstore // 栈顶 long → 槽 n opcode: 0x37
lstore_0 // 栈顶 long → 槽 0 opcode: 0x3F
lstore_1 // 栈顶 long → 槽 1 opcode: 0x40
lstore_2 // 栈顶 long → 槽 2 opcode: 0x41
lstore_3 // 栈顶 long → 槽 3 opcode: 0x42
fstore // 栈顶 float → 槽 n opcode: 0x38
fstore_0 // 栈顶 float → 槽 0 opcode: 0x43
fstore_1 // 栈顶 float → 槽 1 opcode: 0x44
fstore_2 // 栈顶 float → 槽 2 opcode: 0x45
fstore_3 // 栈顶 float → 槽 3 opcode: 0x46
dstore // 栈顶 double → 槽 n opcode: 0x39
dstore_0 // 栈顶 double → 槽 0 opcode: 0x47
dstore_1 // 栈顶 double → 槽 1 opcode: 0x48
dstore_2 // 栈顶 double → 槽 2 opcode: 0x49
dstore_3 // 栈顶 double → 槽 3 opcode: 0x4A
astore // 栈顶 reference → 槽 n opcode: 0x3A
astore_0 // 栈顶引用 → 槽 0 opcode: 0x4B
astore_1 // 栈顶引用 → 槽 1 opcode: 0x4C
astore_2 // 栈顶引用 → 槽 2 opcode: 0x4D
astore_3 // 栈顶引用 → 槽 3 opcode: 0x4E完整执行示例
源码
java
public int add(int a, int b) {
return a + b;
}字节码分析
bash
javap -c Demo.class
# public int add(int, int);
# Code:
# 0: iload_1 // 槽 1(a)入栈
# 1: iload_2 // 槽 2(b)入栈
# 2: iadd // 弹出两个 int,相加,结果入栈
# 3: ireturn // 返回栈顶 int执行过程
执行 iload_1: [a]
执行 iload_2: [a, b]
执行 iadd: [result] // 弹出 b 和 a,相加,result 入栈
执行 ireturn: 返回 result更多示例
java
public class Demo {
public static void main(String[] args) {
int a = 5;
int b = 10;
int c = a + b;
Object obj = null;
double d = 1.0;
}
}字节码:
# public static void main(java.lang.String[]);
# Code:
# 0: iconst_5 // int 5 入栈
# 1: istore_1 // 存到槽 1(a)
#
# 2: bipush 10 // int 10 入栈
# 4: istore_2 // 存到槽 2(b)
#
# 5: iload_1 // a 入栈
# 6: iload_2 // b 入栈
# 7: iadd // 相加
# 8: istore_3 // 存到槽 3(c)
#
# 9: aconst_null // null 入栈
# 10: astore 4 // 存到槽 4(obj)
# // 注意:不是 astore_4,因为槽 4 > 3
#
# 12: dconst_1 // double 1.0 入栈
# 13: dstore 5 // 存到槽 5(d),槽 6 被 d 占用
# // double 占两个槽:5 和 6
#
# 15: returnwide 指令
当局部变量索引超过 255 时,iload_n/istore_n 系列就不够用了,需要 wide 前缀:
java
public void method(int p1, int p2, int p3, ..., int p300) {
return p300; // 槽号超过了 255
}wide iload 300 // 加载槽 300 的 int
wide istore 300 // 存储到槽 300
wide iinc 1 by 100 // 槽 1 的 int 增加 100wide 格式:
0xC4 + iload + index(u1 或 u2)iinc 指令
iinc 是唯一一个直接在局部变量上做运算的指令(不需要经过操作数栈):
iinc 1 by 100 // 槽 1 的 int 增加 100java
// 源码
int i = 0;
i++;
// 字节码
iconst_0
istore_1
iinc 1 by 1 // 直接在槽 1 上加 1(不需要 iload/istore)对比普通的递增:
java
// 源码
int i = 0;
i = i + 1;
// 字节码
iconst_0
istore_1
iload_1
iconst_1
iadd
istore_1 // 需要 4 条指令,而不是 1 条指令的 opcode 分布
理解 opcode 分布有助于理解 JVM 的设计:
| 范围 | 指令类型 |
|---|---|
| 0x00~0x0F | 特例(iconst、aconst_null、bipush 等) |
| 0x10~0x1D | 常量加载(ldc、iload、lload、fload、dload、aload 系列) |
| 0x1E~0x2D | 局部变量加载(lload_0~aload_3) |
| 0x2E~0x35 | 数组加载(iaload、laload 等) |
| 0x36~0x4A | 局部变量存储(istore、lstore、fstore、dstore、astore 系列) |
| 0x4B~0x83 | 更多加载/存储操作 |
本节小结
加载存储指令速查:
| 类别 | 指令 | 作用 |
|---|---|---|
| 常量入栈 | iconst_0~5、bipush、sipush、ldc | 常量 → 栈 |
| 局部变量加载 | iload_n、lload_n、fload_n、dload_n、aload_n | 局部变量 → 栈 |
| 局部变量存储 | istore_n、lstore_n、fstore_n、dstore_n、astore_n | 栈 → 局部变量 |
| 直接运算 | iinc | 局部变量直接增减 |
| 宽化 | wide | 扩展索引范围 |
指令分有 _n 后缀(隐含操作数)和无后缀(显式操作数)两种形式,_n 形式更紧凑,执行更快。
下一节,我们来看 算术/类型转换指令(++运算符/宽化/窄化)。
