Перейти к основному содержимому

Методы <init> и <clinit> в JVM

· 4 мин. чтения

1. Обзор

JVM использует два различных метода для инициализации экземпляров объектов и классов.

В этой быстрой статье мы увидим, как компилятор и среда выполнения используют методы <init> и <clinit> для целей инициализации.

2. Методы инициализации экземпляра

Начнем с простого выделения и назначения объектов:

Object obj = new Object();

Если мы скомпилируем этот фрагмент и посмотрим на его байт-код через javap -c , мы увидим что-то вроде:

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

Для инициализации объекта JVM вызывает специальный метод с именем <init>. На жаргоне JVM этот метод является методом инициализации экземпляра . Метод является инициализацией экземпляра тогда и только тогда, когда:

  • Он определен в классе
  • Его имя < init>
  • Он возвращает пустоту

Каждый класс может иметь ноль или более методов инициализации экземпляра . Эти методы обычно соответствуют конструкторам в языках программирования на основе JVM, таких как Java или Kotlin.

2.1. Конструкторы и блоки инициализатора экземпляра

Чтобы лучше понять, как компилятор 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() {
}
}

Это байт-код для этого класса:

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

Несмотря на то, что конструктор и блоки инициализатора в Java разделены, они находятся в одном и том же методе инициализации экземпляра на уровне байт-кода. Собственно говоря, этот метод <init> :

  • Сначала инициализируются поля firstName и lastName (индекс от 0 до 13) .
  • Затем он выводит что-то на консоль как часть блока инициализатора экземпляра (индексы с 16 по 21).
  • И, наконец, он обновляет переменные экземпляра аргументами конструктора.

Если мы создадим Person следующим образом:

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

Затем это переводится в следующий байт-код:

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

На этот раз JVM вызывает другой метод <init> с сигнатурой, соответствующей конструктору Java.

Ключевым моментом здесь является то, что конструкторы и другие инициализаторы экземпляров эквивалентны методу <init> в мире JVM.

3. Методы инициализации класса

В Java статические блоки инициализатора полезны, когда мы собираемся инициализировать что-то на уровне класса:

public class Person {

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

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

// omitted
}

Когда мы компилируем предыдущий код, компилятор переводит статический блок в метод инициализации класса на уровне байт-кода.

Проще говоря, метод является инициализирующим класс тогда и только тогда, когда:

  • Его имя <clinit>
  • Он возвращает пустоту

Поэтому единственный способ сгенерировать метод <clinit> в Java — это использовать статические поля и инициализаторы статических блоков.

JVM вызывает <clinit> при первом использовании соответствующего класса. Поэтому вызов <clinit> происходит во время выполнения, и мы не можем увидеть вызов на уровне байт-кода.

4. Вывод

В этой быстрой статье мы увидели разницу между методами <init> и <clinit> в JVM. Метод <init> используется для инициализации экземпляров объектов. Кроме того, JVM при необходимости вызывает метод <clinit> для инициализации класса.

Чтобы лучше понять, как работает инициализация в JVM, настоятельно рекомендуется прочитать спецификацию JVM .