每当我查看反编译结果时,经常会看到 dup 字节码指令跟在 new 字节码指令之后,又不知道它是干嘛的,很影响阅读,所以就有了这篇博客~
JVM 中的字节码指令操作是基于操作数栈的,栈操作一个数据只能通过先出栈计算,再将结果压栈的方式。操作数栈主要用于保存执行过程的中间结果:配合局部变量表,将中间结果保存在变量槽中;需要使用局部变量时,读取局部变量表对应变量槽中的元素压入栈顶再出栈。
以下代码创建了 CreateObjectTest 对象,并使用局部变量 createObjectTest 保存实例的引用。
public class CreateObjectTest {
public static void main(String[] args) {
CreateObjectTest createObjectTest = new CreateObjectTest();
}
}
第一段代码反编译结果:
Code:
stack=2, locals=2, args_size=1
0: new #2 new 关键字,在堆中创建 CreateObjectTest 实例,并将该对象的引用压入操作数栈顶 // class object/create/CreateObjectTest
3: dup 复制一份栈顶元素并压栈
4: invokespecial #3 调用无参构造 // Method "<init>":()V
7: astore_1 将栈顶元素存入变量槽1中
8: return
第一步 JVM 执行 new 指令,在堆中开辟内存空间并创建 CreateObjectTest 对象,并将对象对应的引用压入栈顶。
第二步 dup 复制一份栈顶元素并压栈。这里为什么要复制一份呢?往下看
第三步 invokespecial 指令弹出栈顶元素,调用 CreateObjectTest 的无参构造方法,进行对象实例化。
因为无参构造方法是实例方法,jvm 会自动传入调用该实例方法的对象,实例方法中可以通过 this 关键字访问该对象,且该对象位于局部变量表0号变量槽中。
第四步 astore_1 将栈顶元素存入变量槽1中,变量槽0被 String[] args 占用。
这时候 dup 的作用体现了:原栈顶数据 A1(对象引用),经过 dup 指令,栈顶变为 A2。调用无参构造,A2 出栈,A1重新变成栈顶。然后 astore_1 指令将 A1 存入变量槽1中。如果没有 dup 指令,那么数据 A1 在调用无参构造时出栈,astore_1 无法找到数据。
以下代码创建了 CreateObjectTest 对象,没有保存实例的引用。
public class CreateObjectTest {
public static void main(String[] args) {
new CreateObjectTest();
}
}
实例的引用不会被用到第二次,那么是否就不用执行 dup 指令了呢?
Code:
stack=2, locals=1, args_size=1
0: new #2 // class object/create/CreateObjectTest
3: dup
4: invokespecial #3 // Method "<init>":()V
7: pop
8: return
还是执行了 dup 指令,在调用完无参构造后将多余的数据弹出栈顶。
所以我们在任何时刻使用 new 关键字创建对象时,查看对应的反编译结果,都能看到 new + dup + invokespecial 的组合