자바 언어·플랫폼 용어 사전
JVM클래스로더 · 위임 모델 · static 초기화

클래스 로딩

클래스 파일을 읽어 메모리에 올리는 과정. 로딩-링크-초기화 세 단계이며 초기화는 첫 사용까지 미뤄진다.

.class 파일을 읽어 JVM 메모리에 사용 가능한 형태로 올리는 과정.

세 단계

① 로딩(Loading)      클래스 파일을 읽어 Class 객체를 만든다
② 링크(Linking)
     검증(Verification)  바이트코드가 규칙에 맞는지 확인 (보안의 핵심)
     준비(Preparation)   static 필드를 '기본값' 으로 초기화 (int → 0)
     해석(Resolution)    심볼 참조를 실제 참조로 (지연 가능)
③ 초기화(Initialization)  static 초기화 블록과 static 필드 대입을 실행한다

②의 준비와 ③의 초기화가 다르다는 게 핵심이다.

class A { static int x = 10; }
// 준비 단계: x = 0
// 초기화 단계: x = 10

초기화는 언제 일어나나

  • new 로 인스턴스를 만들 때

  • static 필드를 읽거나 쓸 때 (단, final 상수는 컴파일 시점에 치환되어 유발하지 않는다)

  • static 메서드를 호출할 때

  • 리플렉션으로 접근할 때

  • 하위 클래스가 초기화될 때 (상위가 먼저)

  • 클래스를 '로드' 했다고 초기화되는 게 아니다

class Holder { static final Singleton INSTANCE = new Singleton(); }
// Holder 를 처음 참조하는 순간에만 초기화된다
// JVM 이 클래스 초기화의 스레드 안전성을 보장한다 → 락 없는 지연 초기화 (홀더 패턴)

위임 모델

Bootstrap  (java.base 등 핵심)
   ↑ 위임
Platform   (표준 확장)
   ↑ 위임
Application (우리 클래스패스)

요청을 받으면 먼저 '부모에게' 물어보고, 부모가 못 찾을 때만 자기가 찾는다

왜 위임하나 — 누군가 java.lang.String을 직접 만들어 넣어도, 항상 부모(Bootstrap)의 진짜 String이 먼저 로드된다. 핵심 클래스 위조를 막는 보안 장치다.

같은 클래스인가는 로더까지 본다

클래스의 신원 = 패키지 + 이름 + '로더'

같은 이름이어도 로더가 다르면 다른 클래스다

  • ClassCastException: com.A cannot be cast to com.A
  • 웹 컨테이너·플러그인 환경에서 만나는 당황스러운 오류의 정체

실무에서 부딪히는 곳

  • NoClassDefFoundError — 로드는 시도했는데 실패했다 (초기화 중 예외가 흔한 원인)

  • ClassNotFoundException — 아예 못 찾았다 (클래스패스 문제)

  • 앞의 것은 '두 번째 접근부터' 원인 스택이 사라져 추적이 어렵다

    • 첫 발생 시점의 로그를 반드시 확인한다

가상 스레드와의 접점

클래스 로딩·정적 초기화 중에는 가상 스레드가 언마운트할 수 없다 — JEP 491 이후에도 남은 세 pinning 중 하나다.

면접 함정

  • "클래스는 프로그램 시작 시 전부 로드된다" → 필요할 때 로드된다.
  • "이름이 같으면 같은 클래스" → 로더가 다르면 다르다.

초기화 순서를 눈으로 보기

class Parent {
    static { System.out.println("Parent static"); }
    { System.out.println("Parent instance"); }
    Parent() { System.out.println("Parent ctor"); }
}
class Child extends Parent {
    static { System.out.println("Child static"); }
    { System.out.println("Child instance"); }
    Child() { System.out.println("Child ctor"); }
}
new Child();
  • Parent static — ← static 은 클래스당 한 번, 상위가 먼저 Child static

  • Parent instance — ← 인스턴스 초기화 블록 Parent ctor Child instance Child ctor

static은 클래스 초기화 시 한 번, 인스턴스 블록은 생성자마다 실행된다.

상수는 초기화를 유발하지 않는다

class A { static final int X = 10; static { System.out.println("init"); } }
System.out.println(A.X);      // "init" 이 안 찍힌다!
// 컴파일 시점 상수(final + 컴파일 타임 상수식)는 사용처에 값이 박힌다

이 때문에 라이브러리의 상수 값을 바꾸고 재배포했는데 반영이 안 되는 일이 생긴다. 사용하는 쪽도 다시 컴파일해야 한다.

진단

java -verbose:class -jar app.jar | grep MyClass   # 어디서 로드됐나
jcmd <pid> VM.classloader_stats                   # 로더별 클래스 수·메모리

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — JVM 실행 흐름 — 내 코드가 도는 과정