ska*_*ral 3 java jvm jvm-bytecode
假设我们有以下课程:
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);
}
Run Code Online (Sandbox Code Playgroud)
这个类广泛使用局部变量,所有这些都是最终的.
如果我们查看生成的字节码,比如说,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
Run Code Online (Sandbox Code Playgroud)
有很多令人费解的操作码.
现在,假设使用字节码生成库,我为相同的方法生成了以下字节码:
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
Run Code Online (Sandbox Code Playgroud)
从语义上讲,这种新方法实现与旧方法具有相同的含义 - 它仍然需要来自三个传感器的温度值,从它们得到平均值并返回它.但是不是将中间值放到变量上,而是在堆栈上进行所有计算.我可以用这种方式重写它,因为我的所有局部变量和字段都是最终的.
现在有一个问题:如果我正在做一些与字节码生成相关的魔术,并且在所有地方都遵循"堆栈上的所有计算"方法(假设我的所有变量和字段都是最终的),我可能面临哪些潜在的陷阱?
注意:我无意以我描述的方式重写现有Java类的字节码.这里给出的示例类只是为了显示我想在字节码中实现的方法语义.
最大的陷阱:你可能会意外地阻止JIT完成它的工作.
从而实现与目标完全相反的方式:降低运行时性能.
JIT(在某种程度上)是为了为众所周知的常用编码模式创建最佳结果而编写的.如果你的工作更加努力,很可能会做一个不那么理想的工作.
关键是:与其他语言相比,java编译器没有做很多优化步骤.真正的魔法发生在...... JIT开始的时候.因此:你必须非常详细地研究JIT正在做什么,以了解如何创建更好的字节码,以后也可以"JIT".
正如Andreas\xe2\x80\x99 答案所示,Java 代码利用堆栈作为临时值并不罕见,就像在嵌套表达式中一样。这就是为什么以这种方式创建指令集,使用操作数堆栈来隐式引用先前计算的值。事实上,我\xe2\x80\x99d调用你的代码示例与其过度使用局部变量不同寻常。
\n\n如果字节码生成工具的输入不是 Java 代码,则变量的数量可能与典型的 Java 代码不同,特别是当它们具有声明性时,因此不需要将所有变量直接映射到本地变量字节码。
\n\n像 HotSpot 这样的 JVM 将代码传输为SSA 形式,其中局部变量和操作数堆栈之间的所有传输操作以及纯堆栈操作(如dup和swap)在应用后续优化之前都会被消除,因此您可以选择是否使用局部变量不会对性能产生任何影响。
可能值得注意的是,您通常无法在调试器中检查操作数堆栈上的值,因此您可能会考虑在进行调试构建时(也生成时LocalVariableTable)保留变量。
某些代码结构需要局部变量。例如,当您有一个异常处理程序时,其入口点将清除操作数堆栈,仅包含对异常的引用,因此它想要访问的所有值都必须具体化为局部变量。我不知道您的输入形式是否具有循环结构,如果有,您通常会在必要时使用底层的可变变量将它们从声明形式转换为传统循环。注意iinc指令,它直接与局部变量\xe2\x80\xa6 一起工作