새소식

인기 검색어

개발일기

static 영역은 동적 로딩 과정에서 초기화되지 않는다

  • -

"스프링 입문을 위한 자바 객체 지향의 원리와 이해"라는 책을 읽다가 내가 동적 로딩에 대해서 잘못 알고 있었음을 알게 되었다.

 

다음과 같은 소스코드가 있다.

 

public class A {
    public static void main(String[] args) {
        System.out.println("main");
        System.out.println(B.value);
    }
}

class B {
    static int value = 0;

    static {
        System.out.println("B");
    }
}

 

위와 같은 코드를 실행하면, 나는 B -> main -> 0이 호출될 것이라고 생각했으나, 정답은 main -> B -> 0 순으로 호출된다. 처음에는 그 이유가 Java의 동적 로딩(Dynamic loading) 때문이라고 생각했다.

 

그래서 동적 로딩에 대해 내가 잘못 알고 있구나 생각하여 동적 로딩에 대해 공부했다.

 

이전까지는 동적로딩이란 호출하고 있는 정보들의 FQCN을 보고, 필요한 정보들이 없으면 그때 로딩하는 것으로 알고 있었다. 그런데 이 시점이 실행하는 시점이 아니라, 실행하기 전 미리 올려두는 것으로 생각해 B -> main -> 0 순이라고 생각했고, 이게 잘못된 것이라 생각해서 동적 로딩에 대해서 공부해 보았다.

 

다음 글을 읽고 보니 동적 로딩에는 다음과 같이 2가지가 존재한다고 한다. 로드타임 동적로딩(load-time dynamic loading)과 런타임 동적 로딩(run-time dynamic loading) 이렇게 2가지가 있다.

- 클래스 로더 1, 동적인 클래스 로딩과 클래스로더 : https://javacan.tistory.com/entry/1

 

내가 이해한 바로는 로드타임 동적 로딩은 위처럼 클래스 정보가 컴파일시 명시적일 때 동작하는 것이고, 런타임 동적 로딩과 같이 추상형을 바라보고 있거나, 리플랙션 등의 이유로 런타임이 아니라면 확인할 수 없는 경우에 적용되는 것으로 이해했다.

 

실험한 내용

 

그런데 이상한 점이 있다. 다음과 같은 소스는 분명 명시적인 클래스이기 때문에 가설대로라면, 로드타임 동적로딩일 경우 B -> main -> 0 순으로 호출되어야 한다. 그런데 main -> B -> 0 순으로 출력하고 있다.

 

따라서 동적 로딩은 이 문제의 원인이 아닌 것으로 판단하고 다른 조사를 해보았다.

 

오명운님 블로그의 Back to the Essence - Java 컴파일에서 실행까지 - (2) 글을 보면, 다음과 같은 내용이 나온다. 정적 필드를 기본값으로 초기화 하는 것은 링크의 준비 단계에서 수행되고, 정적 필드를 특정값으로 초기화 하는 것은 초기화 단계에서 수행된다. 지금 설명하고 있는 이 "클래스 또는 인터페이스 초기화 메서드"가 실행되는 것이 초기화 단계다.

- 참조 : https://homoefficio.github.io/2019/01/31/Back-to-the-Essence-Java-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EC%97%90%EC%84%9C-%EC%8B%A4%ED%96%89%EA%B9%8C%EC%A7%80-2/

 

즉, 클래스를 불러와서 선언하는 시점과 초기화하는 시점이 다르다는 이야기다. 그럼 이번엔 아예 바이트코드를 까보면서 확인해보자.

 

 

여기서 수상한 부분이 있다. main을 INVOKE하는 부분과 B.value를 불러오는 그 사이에 GETSTATIC하고 sout을 하고 있다. GETSTATIC에 대한 opcode 내용을 확인해보자.

 

https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html#jvms-6.5.getstatic

 

 

GETSTATIC이 처음 호출되면 초기화 작업을 수행하고, 그 후에는 캐시되어 있는 값을 가져와 쓴다는 것 같다.

 

https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-5.html#jvms-5.5

 

위 글은 JVM 초기화 과정에 대한 공식 문서 글이다. 한 번 읽어두면 좋다.

 

위 예제가 나온 책에서는 다음과 같은 설명을 한다.

 

클래스가 제일 처음 사용될 때는 다음 세 가지 경우 중 하나다.
- 클래스의 정적 속성을 사용할 때
- 클래스의 정적 메서드를 사용할 때
- 클래스의 인스턴스를 최초로 만들 때

 

결론 : 로딩하는 과정과 초기화되는 과정은 분리되어 있다.

 

오늘 삽질을 통해 동적 로딩에 대해 애매하게 알던 부분과 JVM 로딩 및 초기화 과정에 대해서 복습할 수 있는 기회가 되었다.

 

 

참조

- The Java Tutorials : https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html

- @BeforeClass vs static{} : https://stackoverflow.com/questions/15493189/beforeclass-vs-static

- Back to the Essence - Java 컴파일에서 실행까지 - (2) : https://homoefficio.github.io/2019/01/31/Back-to-the-Essence-Java-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EC%97%90%EC%84%9C-%EC%8B%A4%ED%96%89%EA%B9%8C%EC%A7%80-2/

- 클래스 로더 1, 동적인 클래스 로딩과 클래스로더 : https://javacan.tistory.com/entry/1

- getstatic opcode : https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html#jvms-6.5.getstatic

- JVM initialization : https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-5.html#jvms-5.5

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.