gony-dev 님의 블로그

클래스 로드 및 초기화 시점 본문

JVM

클래스 로드 및 초기화 시점

minarinamu 2025. 12. 22. 18:04
지난 시간에는 JVM의 프로그램 상에서 작동하는 파라미터 전달 방법 두 가지의 차이를 알아보고,
힙/스택 영역에서 어떻게 동작하는지 알아보았다.

[JVM] - Call by Value / Call by Reference


이번 시간에는 자바 클래스 파일들이 어느 시점에 로딩되고 초기화되는지 알아보도록 하자.
출처 - Inpa Dev

 

Class Loader

로드와 초기화라는 키워드를 듣고 클래스 로더가 생각난다면 JVM에 대해 어느정도 이해를 하고 있다고 할 수 있다.

클래스 로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 Runtime Data Area에 올리는 작업을 수행한다.

동작 방식은 크게 3가지로 나뉜다.

  1. Loading
    • 클래스 파일들을 가져와 JVM의 메모리에 로드한다.
  2. Linking
    1. Verifying - JVM 명세에 명시된 대로 작성되어 있는지 검증한다.
    2. Preparing - 클래스가 필요로 하는 메모리를 할당한다.
    3. Resolving - 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다.
  3. Initialization
    1. 클래스 변수들을 적절한 값으로 초기화한다.

동적으로 할당한다는 것이 클래스 로더에서 중요한 부분인데,
Loading 과정은 모든 메모리를 한 번에 올리지 않고 필요한 경우 동적으로 메모리를 적재한다.

이는 static 멤버들을 소스를 실행하자마자 한 번에 메모리에 올리게 될 경우 비효율적인 성능을 낼 수 있기 때문에 클래스 내의 멤버를 호출하게 되면 그때 클래스가 동적으로 메모리에 로드하는 것이다.

 


클래스 로딩 시점

그럼 클래스를 JVM에 언제 로드할까?

클래스 로딩 시점은 크게 3가지로 나뉜다.

 

1. 클래스의 인스턴스가 생성될 때

2. 클래스의 static 변수가 사용될 때

3. 클래스의 static method가 호출될 때

 

위의 경우들을 예제 코드로 하나씩 살펴보자.

다음은 static 멤버를 로드 및 초기화하기 위해 준비한 Outer.class이다.

public class Outer {

    // 일반 static 필드
    static String value = init("Outer.class", "Outer static 필드");

    // 컴파일 타임 상수 (String literal): Method Area의 상수 풀로 인라인되어
    static final String CONST = "Outer constant 필드";

    Outer() {
        log("Outer 생성자 초기화");
    }

    static void getInstance() {
        log("Outer.getInstance() 메서드 호출");
    }

    // ---- Holder ----
    static class Holder {

        // 클래스 변수
        static String value = "Holder static 필드";

        // 상수
        static final String VALUE_CONST = "Holder constant 필드";

        Holder() {
            log("Holder 생성자 초기화");
        }
    }

    // ---- Inner ----
    class Inner {
        Inner() {
            log("Inner 생성자 초기화");
        }
    }

    // ====== 관찰용 유틸 ======
    private static String init(String name, String v) {
        log("init field -> " + name);
        return v;
    }

    private static void log(String msg) {
        // 어떤 ClassLoader로 로드됐는지까지 같이 출력
        System.out.printf("[%s] %s%n",
                Outer.class.getClassLoader(),
                msg
        );
    }
}

 

클래스 로드의 동작을 자세히 관찰하기 위해서 두 가지 메서드를 추가하였다.

  1. init
    • 해당 객체가 어디로부터 초기화되었는지 출력한다.
  2. log
    • 어떤 클래스로더로 로더되었고, 어떤 동작을 나타내는지 출력한다.

1. 아무것도 출력하지 않았을 경우

public class MyClass {

    public static void main(String[] args) {
        System.out.println("=== main start ===");
    }
}

 

출력결과

 

보는 바와 같이 MyClass만 로드되고 있는 것을 확인할 수 있다.

Outer 클래스의 어떤 객체도 사용하지 않았기 때문에 Outer 클래스는 로드되지 않는다.


2. static 변수 호출하기

public class MyClass {

    public static void main(String[] args) {
        System.out.println("=== main start ===");
        System.out.println("static 필드인 value 값은 " + Outer.value + "입니다.");
        System.out.println("=== main end ===");
    }
}

 

출력결과

 

main 메서드가 실행되자 MyClass를 할당하고 Outer 클래스가 로드되어 static 필드를 출력한다.

 


3. static final 상수 호출하기

static final 필드의 값은 과연 Outer 클래스를 로드할까?

public class MyClass {

    public static void main(String[] args) {
        System.out.println("=== main start ===");
        System.out.println("static final 상수 CONST는 " + Outer.CONST + "입니다.");
        System.out.println("=== main end ===");
    }
}

 

예상했겠지만 Outer Class를 로드하지 않는다.

static final과 같이 선언된 상수는 JVM의 Method Area의 Constant Pool에 따로 저장되어 관리되기 때문이다.


3. 내부 클래스 / static 메서드 호출

public class MyClass {

    public static void main(String[] args) {
        System.out.println("=== main start ===");
        Outer.getInstance();
        new Outer().new Inner();
        System.out.println("=== main end ===");
    }
}

 

위의 코드는 Outer의 static 메서드와 Outer의 내부 클래스를 각각 선언했을 때의 결과이다.

getInstance()의 경우에는 Outer 클래스만 로드하며,

Outer 클래스의 내부 클래스인 Inner 클래스를 생성할 때는 Outer과 Inner 클래스 모두 로드가 된다.

모두 로드가 되기 때문에 Inner 클래스에 static을 설정하지 않는다면 메모리 누수가 발생하게 된다.

 


클래스 초기화 시점

클래스 초기화는 static에 대한 필드 및 메서드의 값을 할당하는 것을 의미한다.

클래스 로더는 클래스 초기화 과정을 3단계로 나누어져 있지만,
클래스 초기화는 클래스 로드 시점과 거의 동일하게 일어남을 위의 예시 코드를 통해 알 수 있었다.

클래스 로더에 대한 단계를 설명하기 위해 기술되어 있었지만 거의 로드와 초기화는 거의 동시에 진행되었다고 보면 된다.

'JVM' 카테고리의 다른 글

Call by Value / Call by Reference  (0) 2025.12.14
자바 코드의 메모리 영역  (1) 2025.12.10
Garbage Collection Tuning  (0) 2025.12.08
Garbage Collection 동작 원리 및 종류  (0) 2025.12.04
JVM 내부 구조 & 메모리 영역  (0) 2025.12.01