티스토리 뷰

Language/JAVA

[JAVA]JVM의 init 메서드?

retto9522 2024. 10. 11. 08:57

JVM의 init 메서드, 객체 초기화를 위한 인스턴스 초기화 메서드

인스턴스 초기화 메서드(instance initialization method)

JVM은 객체 인스턴스를 초기화할 때 init 이라는 고유한 메서드를 활용합니다. 예를 들어, 다음과 같이 Object 객체를 생성하는 코드가 있다고 가정해보겠습니다.

Object obj = new Object();

 

해당 코드를 컴파일 한 후 Byte Code를 살펴보면 다음과 같습니다.

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

 

JVM은 객체의 초기화를 위해 <init> 이라는 특별한 이름의 메서드를 호출하였습니다. 이를 인스턴스 초기화 메서드(instance initialization method)라고 하는데,  아래의 조건을 만족하는 메서드만이 인스턴스 초기화 메서드에 해당합니다.

  • 클래스에 정의되어 있다.
  • 이름이 init이다.
  • 반환 타입이 void이다.

JAVA 공식 문서

 

사실 <init> 메서드는 생성자에 해당하기도 합니다. 자바의 생성자는 자바 컴파일러에 의해 특별한 형태의 메서드인 <init> 메서드로 변환됩니다. 그리고 <init> 메서드는 JVM이 객체를 초기화할 때 호출됩니다. JVM은 이러한 방식을 통해 표준되고 일관된 객체 초기화 과정을 보장하고 있습니다.

하지만, 자바는 인스턴스 초기화 블록(Instance Initalization Block)을 지원합니다. 아래와 같은 Member 클래스가 있다고 할 때, 다음과 같이 인스턴스 초기화 블록을 활용할 수 있습니다.

public class Member {

    private String name;

    // 인스턴스 초기화 블록
    {
        this.printName(name);
    }

    public Member(String name) {
        System.out.println("Member Constructor called");
        this.name = name;
    }

    public void printName(String name) {
        System.out.println("name = " + name);
    }
}

 

Member 객체를 생성해보면 다음과 같이 출력된 것을 확인할 수 있습니다.

name = null
Member Constructor called

 

결과를 보면 생성자 이전에 인스턴스 초기화 블록이 실행된 것을 알 수 있습니다. null이 출력된 이유는 해당 코드이 컴파일 바이트 코드를 보면됩니다. 다음은 컴파일된 .class 파일을 디컴파일한 코드입니다.

public class Member {
    private String name;

    public Member(String name) {
        this.printName(this.name);
        System.out.println("Member Constructor called");
        this.name = name;
    }

    public void printName(String name) {
        System.out.println("name = " + name);
    }
}

 

이를 보면 인스턴스 초기화 블록이 생성자로 들어가게 되고, 아직 name 필드에 값이 할당되지 않은 상태이기 때문에 null이 출력된 것을 알 수 있습니다.

즉, 생성자와 인스턴스 초기화 블록은 코드 작성 시에는 분리되어 있지만, 컴파일 된 Byte Code 수준에선 동일한 인스턴스 초기화 메서드에 해당함을 확인할 수 있는 것입니다. 그럼 다음과 같이 name 필드에 변수가 선언된 클래스라면 어떨까요?

public class Member {

    private String name = "GilDong";

    {
        this.printName(name);
    }

    public Member(String name) {
        System.out.println("Member Constructor called");
        this.name = name;
    }

    public void printName(String name) {
        System.out.println("name = " + name);
    }
}

 

코드를 실행해보면 다음과 같이 출력되는 것을 확인할 수 있습니다.

name = GilDong
Member Constructor called

 

이전과 다르게 name에 값이 할당된 상태임을 확인할 수 있는데, 해당 클래스의 Byte Code를 보면 다음과 같습니다.

0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc           #7                  // String GilDong
7: putfield      #9                  // Field name:Ljava/lang/String;
10: aload_0
11: aload_0
12: getfield      #9                  // Field name:Ljava/lang/String;
15: invokevirtual #15                 // Method printName:(Ljava/lang/String;)V
18: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
21: ldc           #25                 // String Member Constructor called
23: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
26: aload_0
27: aload_1
28: putfield      #9                  // Field name:Ljava/lang/String;
31: return

 

가장먼저 init 메서드를 통해 객체가 생성된 후, name 변수에 "GilDong"값이 할당되었음을 확인할 수 있습니다. 이후, 인스턴스 초기화 블록에 의해 printName()이 호출되고, 곧이어 생성자가 호출됨을 확인할 수 있습니다. 따라서, 객체 생성 이후 값이 바로 할당되기 때문에 null이 찍히지 않는 것입니다. 마찬가지로 컴파일된 .class 파일을 디컴파일한 코드입니다.

public class Member {
    private String name = "MangKyu";

    public Member(String name) {
        this.printName(this.name);
        System.out.println("Member Constructor called");
        this.name = name;
    }

    public void printName(String name) {
        System.out.println("name = " + name);
    }
}

 

참고로 Class의 초기화를 위한 메서드도 존재하는데, 이는 static 블록을 지정해주면 되고, 컴파일 시에 cinit 메서드로 컴파일 됩니다.

public class Member {

    private String name = "GilDong";

    static {
        System.out.println("Class Init called");
    }

    public Member(String name) {
        System.out.println("Member Constructor called");
        this.name = name;
    }

    public void printName(String name) {
        System.out.println("name = " + name);
    }
}

참고

https://mangkyu.tistory.com/366

글 보관함
최근에 올라온 글
Total
Today
Yesterday