java虚拟机指令dup的理解
举个例子:
1 public class ExceptionTest{ 2 3 void cantBeZero(int i) throws Exception{ 4 throw new Exception(); 5 6 } 7 8 }
上面代码编译后的字节码指令如下:
1 void cantBeZero(int) throws java.lang.Exception; 2 descriptor: (I)V 3 flags: 4 Code: 5 stack=2, locals=2, args_size=2 6 0: iload_1 7 1: ifne 12 8 4: new #2 // class java/lang/Exception 9 7: dup 10 8: invokespecial #3 // Method java/lang/Exception."<init>":()V 11 11: athrow 12 12: return
1) 其中new指令在java堆上为Exception对象分配内存空间,并将地址压入操作数栈顶;
2) 然后dup指令为复制操作数栈顶值,并将其压入栈顶,也就是说此时操作数栈上有连续相同的两个对象地址;
3) invokespecial指令调用实例初始化方法<init>:()V,注意这个方法是一个实例方法,所以需要从操作数栈顶弹出一个this引用,也就是说这一步会弹出一个之前入栈的对象地址;
4) athrow指令从操作数栈顶取出一个引用类型的值,并抛出;
5) 最后由return指令结束方法。
从上面的五个步骤中可以看出,需要从栈顶弹出两个实例对象的引用,这就是为什么会在new指令下面有一个dup指令,其实对于每一个new指令来说一般编译器都会在其下面生成
一个dup指令,这是因为实例的初始化方法肯定需要用到一次,然后第二个留给程序员使用,例如给变量赋值,抛出异常等,如果我们不用,那编译器也会生成dup指令,在初始化方法调用完成后再从栈顶pop出来。例如我们仅仅创建一个对象而不做任何操作,例如:
1 void cantBeZero(int i) throws Exception{ 2 new Exception(); 3 4 }
上面的代码仅仅创建了一个Exception对象,而没有做任何操作。
其编译后的字节码指令如下:
1 void cantBeZero(int) throws java.lang.Exception; 2 descriptor: (I)V 3 flags: 4 Code: 5 stack=2, locals=2, args_size=2 6 0: new #2 // class java/lang/Exception 7 3: dup 8 4: invokespecial #3 // Method java/lang/Exception."<init>":()V 9 7: pop 10 8: return
也会生成一个dup指令,只不过在调用完实例初始化方法后,将重复的实例引用又pop出栈了。不过这种情况基本不会出现在我们的代码中,因为我们创建的每一个对象都应该是有用的。
通过上面的例子你应该比较清楚的理解了为什么创建对象时总会有一个dup指令了。