为什么 java 字节码 "store" 后面经常跟着 "load"?
Why does java bytecode "store" often followed by "load"?
当我从一些小的 java 函数读取 jvm 字节码时,我发现当一个新的局部变量被计算在操作数栈上时,假设它将存储在局部变量 table,但通常它会立即加载到操作数堆栈(只是字面上的字节码)。操作不太懂,是不是多余的操作?
查看 dspin
字节码
Method void dspin()
0 dconst_0 // Push double constant 0.0
1 dstore_1 // Store into local variables 1 and 2
2 goto 9 // First time through don't increment
5 dload_1 // Push local variables 1 and 2
6 dconst_1 // Push double constant 1.0
7 dadd // Add; there is no dinc instruction
8 dstore_1 // Store result in local variables 1 and 2
9 dload_1 // Push local variables 1 and 2
10 ldc2_w #4 // Push double constant 100.0
13 dcmpg // There is no if_dcmplt instruction
14 iflt 5 // Compare and loop if less than (i < 100.0)
17 return // Return void when done
store
之后唯一的 load
位于偏移量 9。您可以看到偏移量 9 可以通过两条不同的路径到达:(1) 从偏移量 2 使用 goto 9
;和 (2) 从偏移量 8
开始依次
dload_1
将局部变量1和2的值压入操作数栈(两个变量因为double
):在case(1)第一次尝试进入循环时,以及情况 (2) 在稍后的时间点尝试进入循环时。
有趣的是,在此示例中,如果删除所有 store
和 load
,程序的行为将不会改变。但是,Java 编译器通常不会自作聪明。它或多或少直接编译 Java 代码。在这种情况下,局部变量 i
直接对应于局部变量 1 和 2。
有关详细信息,请参阅 Optimization by Java Compiler。
Java 编译器倾向于以非常简单直接的方式编译事物,将优化留给 JIT。
例如,如果你写 x *= 3; x *= 4;
,你可能会得到类似
的字节码
iload_1
iconst_3
imul
istore_1
iload_1
iconst_4
imul
istore_1
编译器理论上可以找出 store/load 对是多余的并将其删除。但是有几个不这样做的原因 - 1) 这增加了很多复杂性而没有任何好处,因为 JIT 无论如何都会优化所有内容 2) 它使调试变得更加困难,因为您不再能够访问所有局部变量的值 3)如果在这个表达式中间以某种方式抛出异常,则局部变量将具有不正确的值。
看,JVM 中的每个操作都是在操作数堆栈上完成的。所以每当你要对一个变量执行任何操作时,你必须先通过load命令加载(压入)操作数堆栈,然后再执行操作。
这就是为什么store在字节码中跟load指令。
当我从一些小的 java 函数读取 jvm 字节码时,我发现当一个新的局部变量被计算在操作数栈上时,假设它将存储在局部变量 table,但通常它会立即加载到操作数堆栈(只是字面上的字节码)。操作不太懂,是不是多余的操作?
查看 dspin
字节码
Method void dspin()
0 dconst_0 // Push double constant 0.0
1 dstore_1 // Store into local variables 1 and 2
2 goto 9 // First time through don't increment
5 dload_1 // Push local variables 1 and 2
6 dconst_1 // Push double constant 1.0
7 dadd // Add; there is no dinc instruction
8 dstore_1 // Store result in local variables 1 and 2
9 dload_1 // Push local variables 1 and 2
10 ldc2_w #4 // Push double constant 100.0
13 dcmpg // There is no if_dcmplt instruction
14 iflt 5 // Compare and loop if less than (i < 100.0)
17 return // Return void when done
store
之后唯一的 load
位于偏移量 9。您可以看到偏移量 9 可以通过两条不同的路径到达:(1) 从偏移量 2 使用 goto 9
;和 (2) 从偏移量 8
dload_1
将局部变量1和2的值压入操作数栈(两个变量因为double
):在case(1)第一次尝试进入循环时,以及情况 (2) 在稍后的时间点尝试进入循环时。
有趣的是,在此示例中,如果删除所有 store
和 load
,程序的行为将不会改变。但是,Java 编译器通常不会自作聪明。它或多或少直接编译 Java 代码。在这种情况下,局部变量 i
直接对应于局部变量 1 和 2。
有关详细信息,请参阅 Optimization by Java Compiler。
Java 编译器倾向于以非常简单直接的方式编译事物,将优化留给 JIT。
例如,如果你写 x *= 3; x *= 4;
,你可能会得到类似
iload_1
iconst_3
imul
istore_1
iload_1
iconst_4
imul
istore_1
编译器理论上可以找出 store/load 对是多余的并将其删除。但是有几个不这样做的原因 - 1) 这增加了很多复杂性而没有任何好处,因为 JIT 无论如何都会优化所有内容 2) 它使调试变得更加困难,因为您不再能够访问所有局部变量的值 3)如果在这个表达式中间以某种方式抛出异常,则局部变量将具有不正确的值。
看,JVM 中的每个操作都是在操作数堆栈上完成的。所以每当你要对一个变量执行任何操作时,你必须先通过load命令加载(压入)操作数堆栈,然后再执行操作。
这就是为什么store在字节码中跟load指令。