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 사양 을 읽는 것이 좋습니다 .