jvm dup指令的用例

时间:2019-02-20 07:49:55

标签: java jvm bytecode java-bytecode-asm

Java字节码指令集提供various forms of dup instruction。我在理解这些说明和swap说明可能有用时遇到麻烦。 编译这些指令时,哪种Java代码会产生字节码?

3 个答案:

答案 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从不使用swapnop(在当前实现中)。但是仅仅因为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