本文介绍了构造函数字节码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

ASM指南讨论了构造函数:

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();} .此构造函数的字节码是以下:

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

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

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 的值?

How is the value of this already known to the JVM before the first instruction of the constructor?

推荐答案

首先要了解的是对象实例化在字节码级别上的作用.

The first thing to understand, is how object instantiation work on the bytecode level.

JVMS中所述,§3.8.使用类实例:

   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 作为第一个参数的行为.

So the constructor invocation via invokespecial shares the behavior of passing this as the first argument with invokevirtual.

但是,必须强调的是,对未初始化引用的引用要特别对待,因为在调用构造函数(或位于构造函数内部的超级构造函数)之前,不允许您使用它.这是由验证程序强制执行的.

However, it must be emphasized that a reference to an uninitialized reference is treated specially, as you are not allowed to use it before the constructor (or the super constructor when you’re inside the constructor) has been invoked. This is enforced by the verifier.

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

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.

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

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(…))构造函数已被调用(内部类有机会将其外部实例引用初始化为第一个操作),但仍然无法执行其他操作与他们未初始化的.

So code creating an object via the new instruction can’t use it in any way before the constructor has been called, whereas a constructor’s code can only assign fields before another (this(…) or super(…)) constructor has been called (an opportunity used by inner classes to initialize their outer instance reference as a first action), but still can’t do anything else with their uninitialized this.

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

It’s also not allowed for a constructor to return when this is still in the uninitialized state. Hence, the automatically generated constructor bears the required minimum, invoking the super constructor and returning (there is no implicit return on the byte code level).

通常建议阅读Java®虚拟机规范(分别为其 Java 11版本)以及任何特定于ASM的文档或教程.

It’s generally recommended to read The Java® Virtual Machine Specification (resp. its Java 11 version) alongside to any ASM specific documentation or tutorials.

这篇关于构造函数字节码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-29 14:23
查看更多