티스토리 뷰

Java & Kotlin

[JVM] JVM의 init 메서드, 객체의 초기화를 위한 인스턴스 초기화 메서드(instance initialization method)

망나니개발자 2024. 6. 4. 10:00
반응형

 

 

1. JVM의 init 메서드, 객체의 초기화를 위한 인스턴스 초기화 메서드
(instance initialization method)


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

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

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 전문 용어로 이를 인스턴스 초기화 메서드(instance initialization method)라고 하는데, 아래의 조건을 만족하는 메서드만이 인스턴스 초기화 메서드에 해당한다.

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

 

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

하지만 자바는 인스턴스 초기화 블록(Instance Init 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

 

 

코드를 보면 생성자 이전에 인스턴스 초기화 블록이 실행되는 것을 확인할 수 있다. 위와 같이 출력이 된 이유는 해당 코드의 컴파일된 바이트 코드를 보면 된다. 다음은 컴파일된 클래스 파일을 디컴파일한 코드이다.

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);
    }
}

 

 

이를 보면 인스턴스 초기화 블록이 생성자(init 메서드)로 들어가게 되고, 이로 인해 아직 name 필드에 값이 할당되지 않은 상태라 null이 출력됨을 확인할 수 있다. 즉, 생성자와 초기화 블록은 자바 코드 작성 시에는 분리되어 있지만, 컴파일된 바이트 코드 수준에서는 동일한 인스턴스 초기화 메서드(init)에 해당함을 확인할 수 있는 것이다.

그렇다면 다음과 같이 name 필드에 변수가 선언된 클래스라면 어떨까?

public class Member {

    private String name = "MangKyu";

    {
        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 = MangKyu
Member Constructor called

 

 

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

0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc           #7                  // String MangKyu
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 필드에 “MangKyu” 변수가 할당됨을 확인할 수 있다. 그리고 이후에 인스턴스 초기화 블록에 의해 printName가 호출되고, 곧이어 생성자가 호출됨을 확인할 수 있다. 따라서 객체의 생성 이후 값이 바로 할당되기 때문에 null이 찍히지 않는 것이다. 위의 컴파일된 코드를 디컴파일된 형태로 보면 다음과 같다.

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);
    }
}

 

 

참고로 클래스의 초기화를 위한 메서드 역시 존재하는데, 이는 static 블록을 지정해주면 되며, 컴파일 시에 cinit 메서드로 컴파일된다.

public class Member {

    private String name = "MangKyu";

    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);
    }
}

 

 

 

참고 자료

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함