JVM中的dup指令

Lou.Chen
大约 3 分钟

先看一个例子

public class T1 {
    public static void main(String[] args) {
        Object o1=new Object();
    }
}

字节码指令

{
  public org.lc.jvm_test.T1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/lc/jvm_test/T1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    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
      LineNumberTable:
        line 12: 0
        line 13: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
            8       1     1    o1   Ljava/lang/Object;
}
SourceFile: "T1.java"

1)其中new指令在java堆上为Object对象分配内存空间,并将地址压入操作数栈。

2)然后使用dup指令复制操作数栈顶的值,并将其压入栈,也就是说,才是操作数栈上有两个连续相同的对象地址。

3)invokespecial指令调用Object对象的<init>"😦)V实例化方法,注意这里是一个实例化方法,所以需要栈顶的一个地址对象调用该实例化方法并出栈,也就是说这一步会弹出一个之前入栈的对象地址引用。

4)将栈顶的另一个对象地址引用通过astore_1存入到局部变量表中(LocalVariableTable)的slot1位置。即o1

5)最后有return指令结束方法

从上面的五个步骤中可以看出,需要从栈顶弹出两个实例对象的引用

这就是为什么会在new指令下面有一个dup指令,其实对于每一个new指令来说一般编译器都会在其下面生成

一个dup指令,这是因为实例的初始化方法肯定需要用到一次然后第二个留给程序员使用,例如给变量赋值,抛出异常等,如果我们不用,那编译器也会生成dup指令,在初始化方法调用完成后再从栈顶pop出来。例如我们仅仅创建一个对象而不做任何操作,例如:

举个例子

 public class T1 {
        public static void main(String[] args){
             new Object();
        }
    }

上面的代码仅仅创建了一个Object对象,而没有做任何赋值操作

字节码指令


{
  public org.lc.jvm_test.T1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/lc/jvm_test/T1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #2                  // class org/lc/jvm_classloader/Object
         3: dup
         4: invokespecial #3                  // Method org/lc/jvm_classloader/Object."<init>":()V
         7: pop
         8: return
      LineNumberTable:
        line 14: 0
        line 15: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "T1.java"

1)其中new指令在java堆上为Object对象分配内存空间,并将地址压入操作数栈顶;

2)然后dup指令为复制操作数栈顶值,并将其压入栈顶,也就是说此时操作数栈上有连续相同的两个对象地址;

  1. invokespecial指令调用实例初始化方法<init>:()V,注意这个方法是一个实例方法,所以需要从操作数栈顶弹出一个this引用,也就是说这一步会弹出一个之前入栈的对象地址;

4)pop将另一个栈中的对象引用再次弹出

5)return指令结束该方法

**总结:**也会生成一个dup指令,只不过在调用完实例初始化方法后,将重复的实例引用又pop出栈了。不过这种情况基本不会出现在我们的代码中,因为我们创建的每一个对象都应该是有用的。

通过上面的例子你应该比较清楚的理解了为什么创建对象时总会有一个dup指令了。