<init> and <clinit> Methods in the JVM – JVM中的<init> 和<clinit> 方法

最后修改: 2020年 6月 7日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The JVM uses two distinctive methods to initialize object instances and classes.

JVM使用两种独特的方法来初始化对象实例和类。

In this quick article, we’re going to see how the compiler and runtime use the <init> and <clinit> methods for initialization purposes.

在这篇快速文章中,我们将看到编译器和运行时如何使用<init> <clinit> 方法进行初始化。

2. Instance Initialization Methods

2.实例初始化方法

Let’s start with a straightforward object allocation and assignment:

让我们从一个直接的对象分配和赋值开始。

Object obj = new Object();

If we compile this snippet and take a look at its bytecode via javap -c, we’ll see something like:

如果我们编译这个片段,并通过javap -c看一下它的字节码,我们会看到类似的东西。

0: new           #2      // class java/lang/Object
3: dup
4: invokespecial #1      // Method java/lang/Object."<init>":()V
7: astore_1

To initialize the object, the JVM calls a special method named <init>. In JVM jargon, this method is an instance initialization method. A method is an instance initialization if and only if:

为了初始化对象JVM调用了一个名为<init>的特殊方法。用JVM的行话来说,这个方法是一个实例初始化方法。一个方法是一个实例初始化,当且仅当。

  • It is defined in a class
  • Its name is <init>
  • It returns void

Each class can have zero or more instance initialization methods. These methods usually are corresponding to constructors in JVM-based programming languages such as Java or Kotlin.

每个类可以有零个或多个实例初始化方法。这些方法通常与基于JVM的编程语言(如Java或Kotlin)中的构造函数相对应。

2.1. Constructors and Instance Initializer Blocks

2.1.构造函数和实例初始化器区块

To better understand how the Java compiler translates constructors to <init>, let’s consider another example:

为了更好地理解Java编译器如何将构造函数翻译成<init>,让我们考虑另一个例子。

public class Person {
    
    private String firstName = "Foo"; // <init>
    private String lastName = "Bar"; // <init>
    
    // <init>
    {
        System.out.println("Initializing...");
    }

    // <init>
    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    // <init>
    public Person() {
    }
}

This is the bytecode for this class:

这是该类的字节码。

public Person(java.lang.String, java.lang.String);
  Code:
     0: aload_0
     1: invokespecial #1       // Method java/lang/Object."<init>":()V
     4: aload_0
     5: ldc           #7       // String Foo
     7: putfield      #9       // Field firstName:Ljava/lang/String;
    10: aload_0
    11: ldc           #15      // String Bar
    13: putfield      #17      // Field lastName:Ljava/lang/String;
    16: getstatic     #20      // Field java/lang/System.out:Ljava/io/PrintStream;
    19: ldc           #26      // String Initializing...
    21: invokevirtual #28      // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    24: aload_0
    25: aload_1
    26: putfield      #9       // Field firstName:Ljava/lang/String;
    29: aload_0
    30: aload_2
    31: putfield      #17      // Field lastName:Ljava/lang/String;
    34: return

Even though the constructor and the initializer blocks are separate in Java, they are in the same instance initialization method at the bytecode level. As a matter of fact, this <init> method:

尽管构造器和初始化器块在Java中是分开的,但在字节码层面上,它们是在同一个实例初始化方法中。事实上,这个<init> 方法。

  • First, initializes the firstName and lastName fields (index 0 through 13)
  • Then, it prints something to the console as part of the instance initializer block (index 16 through 21)
  • And finally, it updates the instance variables with the constructor arguments

If we create a Person as follows:

如果我们创建一个Person ,如下所示。

Person person = new Person("Brian", "Goetz");

Then this translates to the following bytecode:

那么这就转化为以下字节码。

0: new           #7        // class Person
3: dup
4: ldc           #9        // String Brian
6: ldc           #11       // String Goetz
8: invokespecial #13       // Method Person."<init>":(Ljava/lang/String;Ljava/lang/String;)V
11: astore_1

This time JVM calls another <init> method with a signature corresponding to the Java constructor.

这次JVM调用另一个<init> 方法,其签名与Java构造函数相对应。

The key takeaway here is that the constructors and other instance initializers are equivalent to the <init> method in the JVM world.

这里的关键是构造函数和其他实例初始化器等同于JVM世界中的<init> 方法。

3. Class Initialization Methods

3.类的初始化方法

In Java, static initializer blocks are useful when we’re going to initialize something at the class level:

在Java中,静态初始化块在我们要在类的层次上初始化某些东西时非常有用。

public class Person {

    private static final Logger LOGGER = LoggerFactory.getLogger(Person.class); // <clinit>

    // <clinit>
    static {
        System.out.println("Static Initializing...");
    }

    // omitted
}

When we compile the preceding code, the compiler translates the static block to a class initialization method at the bytecode level.

当我们编译前面的代码时,编译器将静态块翻译成字节码级别的类初始化方法

Put simply, a method is a class initialization one if and only if:

简单地说,一个方法是一个类的初始化方法,当且仅当。

  • Its name is <clinit>
  • It returns void

Therefore, the only way to generate a <clinit> method in Java is to use static fields and static block initializers.

因此,在Java中生成<clinit>方法的唯一方法是使用静态字段和静态块初始化器。

JVM invokes the <clinit> the first time we use the corresponding class. Therefore, the <clinit> invocation happens at runtime, and we can’t see the invocation at the bytecode level.

JVM会在我们第一次使用相应的类时调用<clinit>。因此,<clinit>调用发生在运行时,我们无法在字节码级别看到调用。

4. Conclusion

4.总结

In this quick article, we saw the difference between <init> and <clinit> methods in the JVM. The <init> method is used to initialize object instances.  Also, the JVM invokes the <clinit> method to initialize a class whenever necessary.

在这篇快速文章中,我们看到了JVM中<init> <clinit> 方法之间的区别。<init>方法是用来初始化对象实例的。 此外,JVM在必要时调用<clinit>方法来初始化一个类。

To better understand how initialization works in the JVM, it’s highly recommended to read the JVM specification.

为了更好地理解JVM中的初始化工作,强烈建议阅读JVM规范