Skip to content

加载存储指令(局部变量/常量入栈)

数据搬运工:加载存储指令

如果说 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   (非静态方法才有)
    }
}
类型槽数
intfloatreference1 槽
longdouble2 槽

常量入栈指令

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: 0x08

lconst 系列(0 ~ 1)

lconst_0    // long 0 入栈      opcode: 0x09
lconst_1    // long 1 入栈      opcode: 0x0A

fconst 系列(0 ~ 2)

fconst_0    // float 0.0f 入栈  opcode: 0x0B
fconst_1    // float 1.0f 入栈  opcode: 0x0C
fconst_2    // float 2.0f 入栈  opcode: 0x0D

dconst 系列(0 ~ 1)

dconst_0    // double 0.0 入栈  opcode: 0x0E
dconst_1    // double 1.0 入栈  opcode: 0x0F

aconst_null

aconst_null    // null 引用入栈   opcode: 0x01

bipush 和 sipush

当常量超出 [-1, 5] 的范围时:

bipush 127      // byte 常量入栈 (-128~127)   opcode: 0x10
sipush 32767    // short 常量入栈 (-32768~32767)  opcode: 0x11

ldc:从常量池加载

更大的常量需要从常量池加载:

ldc #3           // 从常量池加载 #3(int/float/string)
ldc_w #255       // 宽化版本,支持更大的索引
ldc2_w #1        // 从常量池加载 long/double
java
// 示例
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: 0x1D

lload 系列

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: 0x21

fload 系列

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: 0x25

dload 系列

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: 0x29

aload 系列

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: return

wide 指令

当局部变量索引超过 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 增加 100
wide 格式:
0xC4 + iload + index(u1 或 u2)

iinc 指令

iinc 是唯一一个直接在局部变量上做运算的指令(不需要经过操作数栈):

iinc 1 by 100    // 槽 1 的 int 增加 100
java
// 源码
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~5bipushsipushldc常量 → 栈
局部变量加载iload_nlload_nfload_ndload_naload_n局部变量 → 栈
局部变量存储istore_nlstore_nfstore_ndstore_nastore_n栈 → 局部变量
直接运算iinc局部变量直接增减
宽化wide扩展索引范围

指令分有 _n 后缀(隐含操作数)和无后缀(显式操作数)两种形式,_n 形式更紧凑,执行更快。

下一节,我们来看 算术/类型转换指令(++运算符/宽化/窄化)

基于 VitePress 构建