dup指令的作用:dup指令可以复制操作数栈栈顶的一个字,再将这个字压入栈。也就是对栈顶的内容做了个拷贝,此时操作数栈上有连续相同的两个对象地址。

JAVA是基于栈的实现,任何操作都是入栈出栈,没有任何寄存器,所以如果要对某一操作数做两次连续操作,那就要复制两次栈顶操作数,比如:

public void test() {
        int x;
        int y = x = 2;
    }

当常数2被压入栈顶后,它要连续两次store到变量x和y,所以这里编译后肯定有一个dup操作,javac、javap -v 后的执行结果:

public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_2
         1: dup
         2: istore_1
         3: istore_2
         4: return

如果不做dup操作,那么istore_1将20存到内存中的x后,再istore_2要么没有操作数,要么是一个其它的操作数。这在编译时对连续操作已经做dup操作了,所以不会出现这个情况。


那么new 指令后,为什么一定要dup操作呢?
因为java代码的new操作编译为虚拟机指令后,虚拟机指令new在堆上分配了内存并在栈顶压入了指向这段内存的地址供任何下面的操作来调用,但是在这个操作数被程序员能访问的操作之前,虚拟机自己肯定要调用对象的 方法,也就是如果程序员做一个 Object o = new Object(); 其实要连续两次对栈顶的操作数进行操作。其中一次是虚拟机内部自动调用的,另一次才是程序员的访问,例如给变量赋值,抛出异常等。

public void get() {
        Object o = new Object();
    }

字节码如下:

public void get();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: return
  1. 其中new指令在java堆上为 Object 对象分配内存空间,并将地址压入操作数栈顶;
  2. 然后dup指令为复制操作数栈顶值,并将其压入栈顶,也就是说此时操作数栈上有连续相同的两个对象地址;
  3. invokespecial指令调用实例初始化方法<init>:()V,注意这个方法是一个实例方法,所以需要从操作数栈顶弹出一个Object对象的引用,作为传给构造器的“this”参数, 也就是说这一步会弹出一个之前入栈的对象地址;(让invokespecial命令知道这个构造方法是刚才创建的那个引用的)
  4. astore_1 指令从操作数栈顶取出 Object 对象的引用并存到局部变量表;
  5. 最后由return指令结束方法。