我不知道javac何时使用它,但是当我们生成代码时,我们经常使用DUP和SWAP。例如,如果您正在执行
Java字节码指令集提供various forms of dup instruction。我在理解这些指令和swap
指令可能有用时遇到麻烦。编译时,哪些Java代码将根据这些指令生成字节码?
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
,则应该准备处理每个合法字节码。
我不知道javac何时使用它,但是当我们生成代码时,我们经常使用DUP和SWAP。例如,如果您正在执行
x.setCharm(y);
x.setSpin(z);
然后您将加载x并立即对其进行DUP,因为调用第一种方法会将其从堆栈中取出,并且您想使用它两次。
SWAP在您执行类似操作时会派上用场
y = x.getCharm(); z.setCharm(y);
第一条指令将y留在堆栈顶部,然后堆栈z和SWAP,因此您现在在堆栈上具有正确的值来调用第二条指令。
dup
的另一个常见用例是数组初始化。
考虑代码int[] a = new int[] {1, 2, 3}
。
iastore指令将整数存储到数组中。它在堆栈上需要三个值:对该数组的引用,该数组中的索引以及要存储的值,并且至关重要的是,它在调用后会弹出所有三个值:
arrayref,index和value从操作数堆栈中弹出。 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
我不知道javac何时使用它,但是当我们生成代码时,我们经常使用DUP和SWAP。例如,如果您正在执行
dup
的另一个常见用例是数组初始化。