Java字节码:局部变量表与堆栈计算
作者:互联网
假设我们有以下课程:
final class Impl implements Gateway3 {
private final Sensor sensor1;
private final Sensor sensor2;
private final Sensor sensor3;
private final Alarm alarm;
public Impl(Sensor sensor1, Sensor sensor2, Sensor sensor3, Alarm alarm) {
this.sensor1 = sensor1;
this.sensor2 = sensor2;
this.sensor3 = sensor3;
this.alarm = alarm;
}
@Override
public Temperature averageTemp() {
final Temperature temp1 = sensor1.temperature();
final Temperature temp2 = sensor2.temperature();
final Temperature temp3 = sensor3.temperature();
final Average tempAvg = new Average.Impl(temp1, temp2, temp3);
final Temperature result = tempAvg.result();
return result;
}
@Override
public void poll() {
final Temperature avgTemp = this.averageTemp();
this.alarm.trigger(avgTemp);
}
这个类广泛使用局部变量,所有这些都是最终的.
如果我们查看生成的字节码,比如说averageTemp方法,我们将看到以下字节码:
0: aload_0
1: getfield #2 // Field sensor1:Lru/mera/avral/script/bytecode/demo/Sensor;
4: invokeinterface #6, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
9: astore_1
10: aload_0
11: getfield #3 // Field sensor2:Lru/mera/avral/script/bytecode/demo/Sensor;
14: invokeinterface #6, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
19: astore_2
20: aload_0
21: getfield #4 // Field sensor3:Lru/mera/avral/script/bytecode/demo/Sensor;
24: invokeinterface #6, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
29: astore_3
30: new #7 // class ru/mera/avral/script/bytecode/demo/Average$Impl
33: dup
34: aload_1
35: aload_2
36: aload_3
37: invokespecial #8 // Method ru/mera/avral/script/bytecode/demo/Average$Impl."<init>":(Lru/mera/avral/script/bytecode/demo/Temperature;Lru/mera/avral/script/bytecode/demo/Temperature;Lru/mera/avral/script/bytecode/demo/Temperature;)V
40: astore 4
42: aload 4
44: invokeinterface #9, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Average.result:()Lru/mera/avral/script/bytecode/demo/Temperature;
49: astore 5
51: aload 5
53: areturn
有很多令人费解的操作码.
现在,假设使用字节码生成库,我为相同的方法生成了以下字节码:
0: new #18 // class ru/mera/avral/script/bytecode/demo/Average$Impl
3: dup
4: aload_0
5: getfield #20 // Field sensor1:Lru/mera/avral/script/bytecode/demo/Sensor;
8: invokeinterface #25, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
13: aload_0
14: getfield #27 // Field sensor2:Lru/mera/avral/script/bytecode/demo/Sensor;
17: invokeinterface #25, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
22: aload_0
23: getfield #29 // Field sensor3:Lru/mera/avral/script/bytecode/demo/Sensor;
26: invokeinterface #25, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
31: invokespecial #33 // Method ru/mera/avral/script/bytecode/demo/Average$Impl."<init>":(Lru/mera/avral/script/bytecode/demo/Temperature;Lru/mera/avral/script/bytecode/demo/Temperature;Lru/mera/avral/script/bytecode/demo/Temperature;)V
34: invokevirtual #36 // Method ru/mera/avral/script/bytecode/demo/Average$Impl.result:()Lru/mera/avral/script/bytecode/demo/Temperature;
37: areturn
从语义上讲,这种新方法实现与旧方法具有相同的含义 – 它仍然需要来自三个传感器的温度值,从它们得到平均值并返回它.但是不是将中间值放到变量上,而是在堆栈上进行所有计算.我可以用这种方式重写它,因为我的所有局部变量和字段都是最终的.
现在有一个问题:如果我正在做一些与字节码生成相关的魔术,并且在所有地方都遵循“堆栈上的所有计算”方法(假设我的所有变量和字段都是最终的),我可能面临哪些潜在的陷阱?
注意:我无意以我描述的方式重写现有Java类的字节码.这里给出的示例类只是为了显示我想在字节码中实现的方法语义.
解决方法:
如Andreas’ answer所示,将Java代码用于临时值的Java代码并不罕见,例如嵌套表达式.这就是为什么指令集是以这种方式创建的,使用操作数堆栈来隐式地引用先前计算的值.实际上,我会调用你的代码示例,因为它过多地使用了局部变量.
如果字节码生成工具的输入不是Java代码,则变量的数量可能与典型的Java代码不同,特别是如果它们具有声明性,因此不需要将所有变量直接映射到本地变量字节码.
像HotSpot这样的JVM将代码传输到SSA form,在应用后续优化之前,无论如何都会消除局部变量和操作数堆栈之间的所有传输操作,以及复制和交换等纯堆栈操作,因此您可以选择使用局部变量或不会对性能产生任何影响.
值得注意的是,您通常无法在调试器中检查操作数堆栈上的值,因此您可能会考虑在进行调试构建时保留变量(当生成LocalVariableTable
时).
一些代码构造需要局部变量.例如.当你有一个异常处理程序时,它的入口点将清除操作数堆栈,只包含对异常的引用,因此它想要访问的所有值都必须具体化为局部变量.我不知道你的输入形式是否有循环结构,如果是这样的话,你通常会在必要时将它们从声明形式转换为使用可变变量的传统循环.记住iinc
instruction,它直接与局部变量一起工作……
标签:java,jvm,jvm-bytecode 来源: https://codeday.me/bug/20190713/1453146.html