jvm dup 指令的用例
Use cases of jvm dup instruction
Java字节码指令集提供various forms of dup instruction。我无法理解这些说明和 swap
说明的用处。
哪些 java 代码在编译时会使用这些指令生成字节码?
我不知道javac什么时候用到它,但是我们在生成代码的时候经常用到DUP和SWAP。例如,如果你正在做相当于
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
的示例
如 this Q&A 中所述,javac
从未使用 swap
或 nop
(在当前实现中)。但是不能仅仅因为 javac
没有使用特定指令,就不能假设没有编译器使用它。
例如还有其他 Java 编译器,例如 ECJ
,但可以有 class 由其他编程语言创建的文件,或者已经是检测工具的结果,当您想要检测时,这些文件就变得相关了运行时的代码。并且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
相反。所以这些指令并不是不需要的奇异结构。当特定代码生成器不使用它们时,这只是一个实现细节。
说到 Instrumentation,请记住 ClassFileTransformer
不能保证接收到与编译器生成的字节码完全相同的字节码,这一点也很重要。它可能是等效的字节码。
所以最重要的是,如果你想实现一个 ClassFileTransformer
,你应该准备好处理每个合法的字节码。
dup
的另一个常见用例是数组初始化。
考虑代码 int[] a = new int[] {1, 2, 3}
。
iastore指令将整数存储到数组中。它需要堆栈上的三个值:对数组的引用、该数组中的索引和要存储的值,最重要的是,它会在调用后弹出所有三个值:
The arrayref, index, and value are popped from the operand stack.
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
Java字节码指令集提供various forms of dup instruction。我无法理解这些说明和 swap
说明的用处。
哪些 java 代码在编译时会使用这些指令生成字节码?
我不知道javac什么时候用到它,但是我们在生成代码的时候经常用到DUP和SWAP。例如,如果你正在做相当于
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
如 this Q&A 中所述,javac
从未使用 swap
或 nop
(在当前实现中)。但是不能仅仅因为 javac
没有使用特定指令,就不能假设没有编译器使用它。
例如还有其他 Java 编译器,例如 ECJ
,但可以有 class 由其他编程语言创建的文件,或者已经是检测工具的结果,当您想要检测时,这些文件就变得相关了运行时的代码。并且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
相反。所以这些指令并不是不需要的奇异结构。当特定代码生成器不使用它们时,这只是一个实现细节。
说到 Instrumentation,请记住 ClassFileTransformer
不能保证接收到与编译器生成的字节码完全相同的字节码,这一点也很重要。它可能是等效的字节码。
所以最重要的是,如果你想实现一个 ClassFileTransformer
,你应该准备好处理每个合法的字节码。
dup
的另一个常见用例是数组初始化。
考虑代码 int[] a = new int[] {1, 2, 3}
。
iastore指令将整数存储到数组中。它需要堆栈上的三个值:对数组的引用、该数组中的索引和要存储的值,最重要的是,它会在调用后弹出所有三个值:
The arrayref, index, and value are popped from the operand stack. 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