본문 바로가기

1. KeyPoint

  • 자바 객체가 생성될때 초기화 되는 순서
  • 생성자가 필요한 이유
  • 초기화 블록이 필요한 이유

 

2. 배경지식

자바 객체 생선 순서를 알아보기 전에 기본적으로 알아야 하는 내용을 간략하게 정리했다.

 

2.1 Static Blocks

클래스가 로딩되고 클래스 변수가 준비된 후 자동으로 실행되는 블록이다. 한 클래스 안에 여러 개의 static블록을 넣을 수 있고 위에서부터 아래로 순서대로 static block을 실행한다.

 

public class ObjectTest {

    private final static String CONSTANT = "TEST";
    private String instant;

    public ObjectTest() {
        System.out.println("No Argument Constructor");
    }

    static {
        System.out.println("Static init Area1");
        System.out.println("Static Area initialize : " + CONSTANT);
    }
    static {
        System.out.println("Static init Area2");
    }

    public static void main(String [] args) {
        new ObjectTest();
    }

}

 

 

 

### 2.2 Initializer block

인스턴스가 생성된 후 자동으로 실행되는 블록이다. 한 클래스 안에 여러 개의 인스턴스 블록을 넣을 수 있다. 인스턴스 변수를 초기화 시키는 코드를 넣을때 사용한다.

```java

public class ObjectTest {
    private String instant;

    public ObjectTest() {
        System.out.println("No Argument Constructor");
    }
    {
        System.out.println("Instant init Area1");
    }
    {
        System.out.println("Instant init Area2");
    }

    public static void main(String [] args) {
        new ObjectTest();
    }

}

 

 

2.3 Constructor

 

인스턴스를 생성한 후 자동으로 호출되는 메서드이다. 여러개의 생성자를 가질 수 있고 단 한개만 호출된다. 클래스를 생성하고 생성자를 작성하지 않으면 컴파일러가 자동으로 기본 생성자를 만들어버린다. 즉, 모든 클래스는 반드시 한개 이상의 생성자를 가지고 있다.

 

 

2.4 생성자가 필요한 이유

 

  1. 객체 생성시 필수입력값을 강제화할 수 있다.
  1. 객체 생성시 초기화 작업.

 

2.5 객체 메모리에 올라가는 과정 

 

인스턴스 메서드는 많은 책이나 블로그에서 code area(method area)에 올라간다고 나와있다. 근데 method area에 적재되는 내용을 확인해보면 perm gen영역에 올라가는 내용이랑 일치한다.

 

 

이 부분이 너무 애매한것 같아서 관련된 내용을 조사해 보다가 https://newbedev.com/is-method-area-still-present-in-java-8 글을 읽고 나름대로 결론을 내렸다.

 

Code Area는 논리적인 영역이지 실제 JVM메모리에 구현된 내용이 아니다. 즉, Java 8을 기준으로 Java 8이전에는 perm gen에 method area의 내용들이 적재되는 것이고, Java 8부터는 metaspace에 method area의 내용이 적재되는 것이다.

 

 

 

 

 

 

3. 객체 초기화 과정 관련코드

  1. 클래스 변수 : 기본값 → 명시적 초기화 → 클래스 초기화 블록
  2. 인스턴스 변수 : 기본값 → 명시적 초기화 → 인스턴스 초기화 블록 → 생성자
public class ObjectTest {

    private final static String CONSTANT = "TEST";
    private String instant;

    public ObjectTest() {
        System.out.println("No Argument Constructor");
    }
    public ObjectTest(int x) {
        System.out.println("With Argument Constructor");
    }

    static {
        System.out.println("Static init Area1");
        System.out.println("Static Area initialize : " + CONSTANT);
    }
    static {
        System.out.println("Static init Area2");
    }

    {
        System.out.println("Instant init Area1");
    }
    {
        System.out.println("Instant init Area2");
    }

    public static void main(String [] args) {
        new ObjectTest();
    }

}

 

 

3.1 초기화 블럭을 바이트코드 관점에서 확인

 

우선 인스턴스 변수를 초기화 하는 방법은 아래와 같다.

 

  1. 생성자에서 초기화
  2. 변수 선언과 함께 초기화
  3. 초기화 블록에서 초기화

 

public class ObjectTest {
    private int value = 1;

    {
        value = 2;
    }

    public ObjectTest() {
        value = 3;
    }

    public static void main(String[] args){
        ObjectTest test = new ObjectTest(); 
                System.out.println(test.value);
    }
}

 

오라클 공식문서 (https://docs.oracle.com/javase/tutorial/java/javaOO/initial.htm 내용을 참고하면 아래와 같은 문장을 확인할 수 있다.

 

인스턴스 초기화 블록의 코드는 컴파일러에 의해 생성자 코드로 복사

 

 

위의 예시코드를 바이트코드로 확인해보면 아래와 같은 결과를 확인할 수 있다. 객체의 최상위 클래스인 Object의 성성자를 가장 먼저 호출한다. 그리고 필드 초기화 값인 1로 초기화 시킨다. 다음은 초기화 블럭 값인 2로 초기화하고 가장 마지막에 생성자로 정의한 3값으로 초기화 시킨다.

 

 

  1. invokespecial #1 // Method java/lang/Object."<init>":()V
  2. putfield #2 // Field value:I
  3. putfield #2 // Field value:I
  4. putfield #2 // Field value:I
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field value:I
         9: aload_0
        10: iconst_2
        11: putfield      #2                  // Field value:I
        14: aload_0
        15: iconst_3
        16: putfield      #2                  // Field value:I
        19: return

 

 

이때 주의해야할 점이 인스턴스 생성시 반드시  필드초기화   다음에  인스턴스 초기화  블록초기화가 이루어지는것이 아니다. 만약 위의 코드를 아래와 같이 변경하면, 필드초기화부터 이루어지는것이 아니라 블록초기화 후에 필드초기화가 이루어지는것을 바이트코드에서 확인할 수 있다. 즉, 필드초기화 블록초기화는  코드에 적힌 라인 순서대 로 초기화되고 생성자에 의해서 초기화 되는 값 이 항상 가장  마지막 에 오는것이다.

 

 

public class ObjectTest {

        {
        value = 2;
    }

    private int value = 1;



    public ObjectTest() {
        value = 3;
    }

    public static void main(String[] args){
        ObjectTest test =
                new ObjectTest(); System.out.println(test.value);
    }
}

 

 

3.2 static 초기화 블럭을 바이트코드 관점에서 확인

 

 

이제 static 초기화 블록과,초기화블록, 생성자 에서 초기화 시킬때 바이트 코드를 확인해보자.

public class ObjectTest {
    private  static value1 = 1;
    private  static int value2 = 0;

    static {
        value1 = 2;
    }

    public ObjectTest() {
        value1 = 4;
        value2 = 3;
    }

    public static void main(String[] args){
        ObjectTest test = new ObjectTest();
        System.out.println(test.value1);
    }
}

 

 

static초기화가 먼저 일어난다. (static블록 초기화도 여기에 포함) 그리고 나서 생성자가 생성된다. 생성자에서는 인스턴스 초기화 블록과 필드 초기화 코드를 복사해오고 생성자 코드에 작성된 순서대로 초기화 작업을 진행한다. 

 

value1 : 1 -> 2 -> 4

value2 : 0 -> 3

 

public boj.ObjectTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: iconst_4
         5: putstatic     #2                  // Field value1:I
         8: iconst_3
         9: putstatic     #3                  // Field value2:I
        12: return
      LineNumberTable:
        line 11: 0
        line 12: 4
        line 13: 8
        line 14: 12

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #4                  // class boj/ObjectTest
         3: dup
         4: invokespecial #5                  // Method "<init>":()V
         7: astore_1
         8: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: aload_1
        12: pop
        13: getstatic     #2                  // Field value1:I
        16: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
        19: return
      LineNumberTable:
        line 17: 0
        line 18: 8
        line 19: 19

 

3. 인스턴스,static 초기화관련 흐름 정리

 

인스턴스초기화와 마찬가지로, static 초기화 블록과 필드초기화 에 어떤 순서의 기준이 있는것이 아니라 코드에 적힌 순서 대로 초기화가 진행된다. static 초기화를 제일 위로 올리면 아래와 같은 결과를 얻을 수 있다. 즉 아래 정리한 순서와 완전히 동일한게 아니라 1번 2번 과정은 코드의 라인 순서에 따라서 2번이 먼저 진행될수도 있는것이다.

 

1. static 초기화 블럭 실행

2. static 필드 초기화 실행

3. 생성자 초기화 실행 (인스턴스 초기화 블럭은 여기로 복사되서 순서대로 실행)

 

 

value1 : 2 -> 1 -> 4

value2 : 0 -> 3

instanceValue : 8 -> 9

 


public class ObjectTest {


    static {
        value1 = 2;
    }

    private  static int value1 = 1;
    private  static int value2 = 0;
    private int instanceValue = 8;

    public ObjectTest() {
        instanceValue = 9;
        value1 = 4;
        value2 = 3;

    }

    public static void main(String[] args){
        ObjectTest test = new ObjectTest();
        System.out.println(test.value1);
    }
}

 

 public boj.ObjectTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: bipush        8
         7: putfield      #2                  // Field instanceValue:I
        10: aload_0
        11: bipush        9
        13: putfield      #2                  // Field instanceValue:I
        16: iconst_4
        17: putstatic     #3                  // Field value1:I
        20: iconst_3
        21: putstatic     #4                  // Field value2:I
        24: return
      LineNumberTable:
        line 14: 0
        line 12: 4
        line 15: 10
        line 16: 16
        line 17: 20
        line 19: 24



  static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_2
         1: putstatic     #3                  // Field value1:I
         4: iconst_1
         5: putstatic     #3                  // Field value1:I
         8: iconst_0
         9: putstatic     #4                  // Field value2:I
        12: return
      LineNumberTable:
        line 7: 0
        line 10: 4
        line 11: 8
}

 

결국, 바이트코드 관점에서는  1. static 초기화 2. 생성자 초기화의 단계로 초기화를 진행하는 것이다.

 

 

4. 정리

  • 자바 코드 관점에서는 static blockinstance block생성자 순으로 초기화 된다.
  • 바이트 코드 관점에서는 static block생성자 순으로 초기화 된다
    • 인스턴스 초기기화 블록과 필드초기화의 코드는 컴파일러에 의해 생성자 코드로 복사
  • 초기화 블록이 필요한 이유는 정확하게 모르겠다.
    • 인스턴스 초기화 블록으로 초기화하는 코드는 진짜 본적이 없다. Java언어가 설계될때 어떤 이유에서 인스턴스 초기화 블록을 설계에 포함했지만 잘 사용하지 않는것 같다.

 

5. 참고

https://newbedev.com/is-method-area-still-present-in-java-8

'Java' 카테고리의 다른 글

Java에서 동기화를 보장하는 방법  (0) 2021.11.05
Java 배열에 대해서  (0) 2021.08.27
자바 String 클래스에 대해서  (1) 2021.08.27
Thread Dump로 DeadLock 확인하기  (0) 2021.06.12
자바 - enum  (0) 2020.09.01

댓글