【问题标题】:Constructor bytecode构造函数字节码
【发布时间】:2018-11-09 00:01:02
【问题描述】:

ASM guide 谈论构造函数:

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

Bean 类还有一个默认的公共构造函数,它是 由编译器生成,因为没有定义显式构造函数 由程序员。这个默认的公共构造函数生成为 Bean() { super(); }。这个构造函数的字节码是 以下:

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

第一条指令将this 压入操作数堆栈。第二 指令从堆栈中弹出这个值,并调用&lt;init&gt; Object 类中定义的方法。这对应于super() 调用,即调用超类的构造函数Object。你 可以在这里看到构造函数在编译和 源类:在编译的类中,它们总是被命名为&lt;init&gt;, 而在源类中,它们具有它们所在的类的名称 被定义。最后最后一条指令返回给调用者。

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

【问题讨论】:

    标签: java java-bytecode-asm bytecode-manipulation jvm-bytecode


    【解决方案1】:

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

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

    【讨论】:

    • 你之前描述的所有这些过程都不是作为字节码发生的吗?
    • @rapt 确实如此,但不是在构造函数本身中。它发生在调用构造函数的代码中。
    【解决方案2】:

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

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

    Java 虚拟机类实例是使用 Java 虚拟机的 new 指令创建的。回想一下,在 Java 虚拟机级别,构造函数以编译器提供的名称 &lt;init&gt; 的方法出现。这种特殊命名的方法称为实例初始化方法 (§2.9)。对于给定的类,可能存在对应于多个构造函数的多个实例初始化方法。一旦创建了类实例并且其实例变量(包括类及其所有超类的变量)已初始化为其默认值,则调用新类实例的实例初始化方法。例如:

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

    编译为:

       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 调用的构造函数与invokevirtual 共享将this 作为第一个参数传递的行为。

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

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

    myClass 类的实例初始化方法 (§2.9) 将新的未初始化对象视为其局部变量 0 中的 this 参数。在该方法调用 myClass 或其直接超类的另一个实例初始化方法之前在this 上,该方法可以对this 执行的唯一操作是分配在myClass 中声明的字段。

    在对实例方法进行数据流分析时,验证器初始化局部变量 0 以包含当前类的对象,或者,对于实例初始化方法,局部变量 0 包含指示未初始化对象的特殊类型。在此对象上(从当前类或其直接超类)调用适当的实例初始化方法后,此特殊类型在验证器的操作数堆栈模型和局部变量数组中的所有出现都将替换为当前类类型。验证器拒绝在新对象初始化之前使用它或多次初始化该对象的代码。此外,它确保方法的每个正常返回都调用了该方法的类或直接超类中的实例初始化方法。

    类似地,作为 Java 虚拟机指令 new 的结果,创建一个特殊类型并将其推送到验证器的操作数堆栈模型上。特殊类型表示创建类实例的指令和创建的未初始化类实例的类型。当在该类实例上调用在未初始化的类实例的类中声明的实例初始化方法时,所有出现的特殊类型都将替换为该类实例的预期类型。随着数据流分析的进行,这种类型的变化可能会传播到后续指令。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-21
      • 1970-01-01
      • 2016-07-05
      • 2017-01-13
      相关资源
      最近更新 更多