Java字节码指令集提供various forms of dup instruction。我在理解这些说明和swap
说明可能有用时遇到麻烦。
编译这些指令时,哪种Java代码会产生字节码?
答案 0 :(得分:1)
我不知道javac何时使用它,但是当我们生成代码时,我们经常使用DUP和SWAP。例如,如果您要做的是
x.setCharm(y);
x.setSpin(z);
然后,您将加载x并立即对其进行DUP,因为调用第一种方法会将其从堆栈中取出,并且您想使用它两次。
当您执行类似操作时,SWAP会派上用场
y = x.getCharm();
z.setCharm(y);
第一个指令将y放在堆栈顶部,然后堆栈z和SWAP,因此您现在在堆栈上具有正确的值来调用第二个指令。
答案 1 :(得分:1)
dup
的变量可以出现在普通的Java代码中。
例如如this answer所述,对象实例化通常使用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
如this Q&A中所述,javac
从不使用swap
或nop
(在当前实现中)。但是仅仅因为javac
不使用特定指令,就不能假定没有编译器使用该指令。
例如还有其他Java编译器,例如ECJ
,但是可能存在其他编程语言创建的类文件,或者已经是检测工具的结果,当您要在运行时检测代码时,这些文件就变得很重要。未来的javac
版本也可以使用以前未使用过的指令,就像Java 8之前的Java代码未使用invokedynamic
一样。
This discussion指向一种方案,其中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
,则应该准备处理所有合法字节码。
答案 2 :(得分:0)
dup
的另一个常见用例是数组初始化。
考虑代码int[] a = new int[] {1, 2, 3}
。
iastore指令将整数存储到数组中。它在堆栈上需要三个值:对该数组的引用,该数组中的索引以及要存储的值,并且至关重要的是,它在调用后会弹出所有三个值:
从操作数堆栈中弹出arrayref,索引和值。 Java Virtual Machine Specification
将以上示例转换为字节码的一种幼稚方式可能是这样的:
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
请注意,每条iastore
指令都会重新加载对数组的引用。
可以使用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