Java字节码指令集提供了各种形式的dup指令。我不太理解这些指令和swap
指令的用途。当编译时,哪些Java代码会产生带有这些指令的字节码?
x.setCharm(y);
x.setSpin(z);
如果你想要加载x并立即重复使用它,那么需要在第一次调用方法之前将其DUP,因为第一次调用会将其从堆栈中取出,而你想要使用两次。
当你需要执行类似于以下操作时,SWAP很有用:
y = x.getCharm();
z.setCharm(y);
首先,第一条指令会将 y 放在栈顶,然后你需要把 z 入栈,并执行 SWAP 操作,这样你就能够正确地调用第二条指令。
dup
的变体可能会出现在普通Java代码中。
例如,如此答案所详细说明的那样,对象实例化通常使用dup
,因为new Object()
被编译为
new #n // n referencing Class java.lang.Object in the pool
dup
invokespecial #m // m referencing Method java.lang.Object.<init>()V
intArray[expression]++
会被编译为:… (code pushing the results of intArray and expression)
dup2
iaload
iconst_1
iadd
iastore
还有一些更加高级的
public static long example3(Long[] array, int i, long l) {
return array[i]=l;
}
编译为
0: aload_0
1: iload_1
2: lload_2
3: invokestatic #3 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
6: dup_x2
7: aastore
8: invokevirtual #4 // Method java/lang/Long.longValue:()J
11: lreturn
将数组类型更改为long[]
将产生dup2_x2
的示例。
如此Q&A中所讨论的那样,javac
从不使用swap
或nop
(在当前实现中)。但是仅因为javac
不使用特定指令,您不能假设没有编译器使用它。
例如,还有其他Java编译器,如ECJ
,但可能会创建由其他编程语言创建的类文件或已经是工具的结果,这在您想要在运行时检测代码时变得重要。而且,未来版本的javac
也可以使用以前没有使用过的指令,就像在Java 8之前,Java代码没有使用invokedynamic
一样。
此讨论指出了一种情况,在该情况下swap
是适当的。当使用try-with-resource时,将生成处理已捕获异常的代码,同时已经存在一个已捕获的异常。当前的javac
将其编译为(基本上)
astore n
aload o
aload n
invokevirtual #x // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
其中o
是持有已捕获异常的旧变量,而n
将成为全新的变量,在编译此代码时不需要该变量。
aload o
swap
invokevirtual #x // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
所以说,这些指令并不是从未需要的外来构造。当特定代码生成器不使用它们时,这只是一种实现细节。
谈到仪器化,还要记住 ClassFileTransformer
不保证接收与编译器生成的完全相同的字节码。它可能是等效的字节码。
因此,总之,如果你想要实现一个 ClassFileTransformer
,你应该准备处理每个合法的字节码。
dup
的另一个常见用途是数组初始化。
考虑代码 int[] a = new int[] {1, 2, 3}
。
iastore 指令将整数存储到数组中。它需要栈上的三个值:对数组的引用、该数组中的索引和要存储的值,并且在调用后弹出这三个值:
数组引用、索引和值从操作数栈中弹出。 Java虚拟机规范
将上述示例转换为字节码的一种天真的方法可能如下所示:
iconst_3
newarray int
iconst_0 # array index
iconst_1 # value
iastore # a[0] = 1
aload_1 # load array ref again
iconst_1
iconst_2
iastore # a[1] = 2
aload_1 # load array ref yet again
iconst_2
iconst_3
iastore # a[2] = 3
...
astore_1
dup
来避免。在将索引和值推送到堆栈之前,我们在堆栈上复制数组引用,这样底部条目就留给下一个iastore指令重复使用:iconst_3
newarray int
dup
iconst_0
iconst_1
iastore # a[0] = 1
dup
iconst_1
iconst_2
iastore # a[1] = 2
dup
iconst_2
iconst_3
iastore # a[2] = 3
...
astore_1
dup
指令比从本地变量加载要稍微“便宜”一些。我尝试使用两个版本(aload
vs. dup
)对此进行基准测试(使用1,000个值的int数组初始化1,000,000次),但我的结果并不确定。差异可能更多地体现在最佳实践方面(避免重新加载已经可用的内容),而不是真正的性能影响。虽然我可能是错的。 - martin
dup
。此外,intArray [expression] ++
编译为…,dup2,iaload,iconst_1,iadd,iastore
。如此问答中所讨论的,javac
从未使用swap
或nop
(在当前实现中)。 - Holgerdup
的x
变体,例如dup_x1
、dup2_x1
等怎么处理?我正在为 Java 语言编写一个工具,如果我能忽略这些指令,那会让我的生活变得轻松得多。 - sagajavac
没有使用特定的指令,你不能假设没有编译器使用它。例如,还有其他Java编译器,比如ECJ,但也可能有其他编程语言创建的类文件或已经是一个工具的结果。而且事情是会变化的。在Java 8之前,Java代码没有使用invokedynamic
。 - Holger