构造函数字节码

Constructor bytecode

ASM guide 关于构造函数的讨论:

package pkg;
public class Bean {
  private int f;
  public int getF() {
      return this.f;
  }
  public void setF(int f) {
      this.f = f;
  }
}

The Bean class also has a default public constructor which is generated by the compiler, since no explicit constructor was defined by the programmer. This default public constructor is generated as Bean() { super(); }. The bytecode of this constructor is the following:

ALOAD 0
INVOKESPECIAL java/lang/Object <init> ()V
RETURN

The first instruction pushes this on the operand stack. The second instruction pops this value from the stack, and calls the <init> method defined in the Object class. This corresponds to the super() call, i.e. a call to the constructor of the super class, Object. You can see here that constructors are named differently in compiled and source classes: in compiled classes they are always named <init>, while in source classes they have the name of the class in which they are defined. Finally the last instruction returns to the caller.

在构造函数的第一条指令之前,JVM 如何知道 this 的值?

在 JVM 级别,首先分配对象,取消初始化,然后在该对象上调用构造函数。构造函数或多或少是在未初始化对象上执行的实例方法。

即使在 Java 语言中,this 也存在并且其所有字段都位于构造函数的第一行。

首先要了解的是对象实例化是如何在字节码级别上工作的。

JVMS, §3.8. Working with Class Instances 中所述:

Java Virtual Machine class instances are created using the Java Virtual Machine's new instruction. Recall that at the level of the Java Virtual Machine, a constructor appears as a method with the compiler-supplied name <init>. This specially named method is known as the instance initialization method (§2.9). Multiple instance initialization methods, corresponding to multiple constructors, may exist for a given class. Once the class instance has been created and its instance variables, including those of the class and all of its superclasses, have been initialized to their default values, an instance initialization method of the new class instance is invoked. For example:

   Object create() {
       return new Object();
   }

compiles to:

   Method java.lang.Object create()
   0   new #1              // Class java.lang.Object
   3   dup
   4   invokespecial #4    // Method java.lang.Object.<init>()V
   7   areturn

因此,通过 invokespecial 的构造函数调用共享将 this 作为第一个参数传递给 invokevirtual 的行为。

但是,必须强调的是,对未初始化引用的引用是特殊对待的,因为在调用构造函数(或在构造函数内部时的超级构造函数)之前不允许使用它。这是由验证者强制执行的。

JVMS, §4.10.2.4. Instance Initialization Methods and Newly Created Objects:

… The instance initialization method (§2.9) for class myClass sees the new uninitialized object as its this argument in local variable 0. Before that method invokes another instance initialization method of myClass or its direct superclass on this, the only operation the method can perform on this is assigning fields declared within myClass.

When doing dataflow analysis on instance methods, the verifier initializes local variable 0 to contain an object of the current class, or, for instance initialization methods, local variable 0 contains a special type indicating an uninitialized object. After an appropriate instance initialization method is invoked (from the current class or its direct superclass) on this object, all occurrences of this special type on the verifier's model of the operand stack and in the local variable array are replaced by the current class type. The verifier rejects code that uses the new object before it has been initialized or that initializes the object more than once. In addition, it ensures that every normal return of the method has invoked an instance initialization method either in the class of this method or in the direct superclass.

Similarly, a special type is created and pushed on the verifier's model of the operand stack as the result of the Java Virtual Machine instruction new. The special type indicates the instruction by which the class instance was created and the type of the uninitialized class instance created. When an instance initialization method declared in the class of the uninitialized class instance is invoked on that class instance, all occurrences of the special type are replaced by the intended type of the class instance. This change in type may propagate to subsequent instructions as the dataflow analysis proceeds.

因此,通过 new 指令创建对象的代码在调用构造函数之前不能以任何方式使用它,而构造函数的代码只能在另一个 ( this(…)super(…)) 构造函数已被调用(内部 类 使用的机会将其外部实例引用初始化为第一个操作),但仍无法对未初始化的对象执行任何其他操作这个。

this 仍处于未初始化状态时,构造函数 return 也是不允许的。因此,自动生成的构造函数承担所需的最小值,调用超级构造函数并 returning(在字节码级别上没有隐式 return)。

通常建议阅读 The Java® Virtual Machine Specification (resp. its Java 11 version) 以及任何 ASM 特定文档或教程。