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 전문 용어로 이 메서드는 인스턴스 초기화 메서드 입니다. 메소드는 다음과 같은 경우에만 인스턴스 초기화입니다.

  • 클래스에 정의되어 있습니다.
  • 이름은  < 초기화> 입니다.
  • 무효 를 반환합니다 

각 클래스는 0개 이상의 인스턴스 초기화 메서드를 가질 수 있습니다 . 이러한 메서드는 일반적으로 Java 또는 Kotlin과 같은 JVM 기반 프로그래밍 언어의 생성자에 해당합니다.

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이  Java 생성자에 해당하는 서명을 사용하여 다른 <init>  메서드를 호출합니다.

여기서 중요한 점은 생성자 및 기타 인스턴스 초기화 프로그램이  JVM 세계 의 <init>  메서드와 동일하다는 것입니다.

3. 클래스 초기화 방법

Java에서 정적 초기화 블록 은 클래스 수준에서 무언가를 초기화할 때 유용합니다.

public class Person {

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

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

    // omitted
}

이전 코드를 컴파일할 때 컴파일러는 정적 블록을 바이트 코드 수준에서 클래스 초기화 메서드 로 변환합니다.

간단히 말해서 메소드는 다음과 같은 경우에만 클래스 초기화입니다.

  • 이름은  <클리닛>
  • 무효 를 반환합니다 

따라서 Java에서 <clinit> 메서드 를 생성하는 유일한 방법   은 정적 필드와 정적 블록 초기화 프로그램을 사용하는 것입니다.

JVM은   해당 클래스를 처음 사용할 때 <clinit> 를 호출합니다. 따라서  <clinit>  호출은 런타임에 발생하며 바이트 코드 수준에서 호출을 볼 수 없습니다.

4. 결론

이 빠른 기사에서 우리는 JVM에서 <init>  와  <clinit>  메소드 의 차이점을 보았습니다  . < init>  메서드는 개체 인스턴스를 초기화하는 데 사용됩니다. 또한 JVM은 필요할 때마다 <clinit>  메소드를 호출하여 클래스를 초기화합니다.

JVM에서 초기화가 작동하는 방식을 더 잘 이해하려면 JVM 사양 을 읽는 것이 좋습니다 .

Generic footer banner