클래스 로더의 재정의: 구조, 원리 및 실용적 활용

17.1 클래스 로더의 기본 개념

클래스 로더는 자바 가상 머신(JVM) 내에서 클래스를 로드하는 핵심 구성 요소이다. 모든 클래스는 클래스 로더를 통해 메모리에 로드되며, 이 과정에서 바이트 코드를 읽어들여 java.lang.Class 인스턴스로 변환한다. 이후 해당 클래스는 링크 및 초기화 단계를 거쳐 실행 가능 상태가 된다.

클래스 로더는 클래스의 로딩 단계에만 영향을 미치며, 링크 또는 초기화 동작은 직접 제어할 수 없다. 실행 여부는 실행 엔진(Execution Engine)이 결정한다.

대기업 면접 질문 정리

  • 아리바바: 클래스 로더의 작동 원리, 이중 위임 모델 분석
  • 바이두: 존재하는 클래스 로더 유형과 각각의 로드 대상 확인
  • 텐센트: 이중 위임 모델의 의미와 목적 설명
  • 샤오미: 이중 위임 모델의 개요 설명
  • 디디: 이중 위임 모델 간단 소개 및 장점 설명
  • 징비타오: 클래스 로더의 정의 및 종류
  • 징둥: 이중 위임 모델의 의미 설명

17.2 클래스 로딩 방식 분류

클래스 로딩은 명시적 로딩과 암시적 로딩으로 나뉜다.

  • 명시적 로딩: 프로그램 내에서 직접 ClassLoader 메서드를 호출하여 클래스를 로드. 예: Class.forName() 또는 getClassLoader().loadClass().
  • 암시적 로딩: 클래스 사용 시 자동으로 참조된 클래스가 로드됨. 예: User user = new User();에서 User 클래스를 로드하면, 그 내부에서 참조한 다른 클래스도 자동으로 로드된다.
// 암시적 로딩
User user = new User();

// 명시적 로딩 (초기화 포함)
Class<?> clazz = Class.forName("com.test.java.User");

// 명시적 로딩 (초기화 없음)
ClassLoader.getSystemClassLoader().loadClass("com.test.java.Parent");

17.3 클래스 로더의 필요성

개발자에게는 직접 클래스 로더를 다룰 일이 드물지만, 다음과 같은 이유로 이해가 필수적이다:

  • ClassNotFoundExceptionNoClassDefFoundError 발생 시 문제 진단이 가능해짐.
  • 런타임 시 동적 로딩 또는 바이트코드 암호화/복호화가 필요할 때 활용 가능.
  • 사용자 정의 로직을 구현하기 위해 커스텀 클래스 로더를 작성할 수 있음.

17.4 이름 공간과 고유성

클래스의 고유성은 클래스 로더 + 클래스 자체로 결정된다. 동일한 .class 파일이라도 서로 다른 로더에 의해 로드되면, 서로 다른 타입으로 취급된다.

  • 각 클래스 로더는 독립적인 이름 공간을 가짐.
  • 동일한 패키지 경로를 가진 클래스라도, 다른 로더에 의해 로드되면 서로 다른 타입.
  • 여러 버전의 라이브러리를 동시에 사용하는 경우에 유용 (예: 웹 서버에서 별도의 애플리케이션 간 격리).

17.5 클래스 로딩의 기본 특성

  • 이중 위임 모델(Dual Delegation): 기본적으로 부모 로더에게 요청을 위임하고, 실패 시 자신이 로드.
  • 가시성: 하위 로더는 상위 로더가 로드한 클래스를 접근 가능하나, 반대는 불가능.
  • 유일성: 상위 로더가 이미 로드한 클래스는 하위 로더에서 중복 로드되지 않음. 그러나 서로 다른 로더 간에는 중복 가능.

17.6 클래스 로더의 계층 구조

JVM에서는 다음과 같은 주요 로더가 존재:

// Launcher 클래스의 핵심 코드
Launcher.ExtClassLoader extLoader = Launcher.ExtClassLoader.getExtClassLoader();
this.loader = Launcher.AppClassLoader.getAppClassLoader(extLoader);
Thread.currentThread().setContextClassLoader(this.loader);
  • Bootstrap: C/C++로 구현, 부모 없음.
  • Extension: 부모는 Bootstrap, java.ext.dirs 경로에서 로드.
  • Application(App): 부모는 Extension, classpath 또는 java.class.path 경로에서 로드.

⚠️ 이 계층 관계는 상속이 아니라 포함 관계이며, 하위 로더는 상위 로더의 참조를 포함한다.

public class ClassLoader {
    protected ClassLoader parent;

    public ClassLoader(ClassLoader parent) {
        this.parent = parent;
    }
}

17.7 클래스 로더의 종류

17.7.1 부트스트랩 로더

  • C/C++로 구현, JVM 내부에 포함.
  • JAVA_HOME/jre/lib/rt.jar 또는 sun.boot.class.path 경로의 핵심 라이브러리 로드.
  • java.lang.ClassLoader를 상속하지 않으며, 부모가 없음.
  • java, javax, sun 등 특정 패키지만 허용.
System.out.println("=== 부트스트랩 로더 경로 ===");
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
    System.out.println(url.toExternalForm());
}

// 예시: java.security.Provider 클래스의 로더 확인
ClassLoader loader = java.security.Provider.class.getClassLoader();
System.out.println(loader); // null → 부트스트랩 로더

17.7.2 확장 로더

  • Java로 구현, sun.misc.Launcher$ExtClassLoader 클래스.
  • 부모: 부트스트랩 로더.
  • java.ext.dirs 시스템 속성 또는 jre/lib/ext 디렉터리의 JAR 파일을 로드.
System.out.println("=== 확장 로더 경로 ===");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
    System.out.println(path);
}

// 예시: 확장 로더로 로드된 클래스 확인
ClassLoader extLoader = sun.security.ec.CurveDB.class.getClassLoader();
System.out.println(extLoader); // sun.misc.Launcher$ExtClassLoader@...

17.7.3 시스템 로더

  • Java로 구현, sun.misc.Launcher$AppClassLoader.
  • 부모: 확장 로더.
  • classpath 또는 java.class.path 경로의 클래스를 로드.
  • 기본 애플리케이션 로더이며, 커스텀 로더의 기본 부모로 사용.
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemLoader); // sun.misc.Launcher$AppClassLoader@...

17.7.4 사용자 정의 로더

  • 커스텀 로더는 java.lang.ClassLoader를 상속하여 구현.
  • 네트워크, 데이터베이스, 암호화된 파일 등 다양한 소스에서 클래스를 로드 가능.
  • 플러그인 아키텍처(예: OSGi, Eclipse), 애플리케이션 격리(예: Tomcat), 동적 로딩 등에 활용.
public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) throw new ClassNotFoundException();
        return defineClass(name, classData, 0, classData.length);
    }

    private byte[] loadClassData(String className) {
        // 외부 소스에서 바이트코드 읽기 로직
        return null;
    }
}

17.8 클래스 로더 확인 방법

// 현재 클래스의 로더
clazz.getClassLoader()

// 현재 스레드의 컨텍스트 로더
Thread.currentThread().getContextClassLoader()

// 시스템 로더
ClassLoader.getSystemClassLoader()

배열 클래스의 경우, 요소 타입의 로더와 동일한 로더를 사용하거나, 기본 타입일 경우 로더 없음.

String[] arr = new String[1];
System.out.println(arr.getClass().getClassLoader()); // null

int[] ints = new int[1];
System.out.println(ints.getClass().getClassLoader()); // null

17.9 ClassLoader의 주요 메서드

  • getParent(): 부모 로더 반환.
  • loadClass(String name): 이중 위임 모델을 기반으로 클래스 로드.
  • findClass(String name): 커스텀 로딩 로직을 구현할 때 오버라이드.
  • defineClass(String name, byte[] data): 바이트코드를 Class 객체로 변환.
  • resolveClass(Class<?> c): 클래스 연결 수행.

권장: loadClass()는 수정하지 않고, findClass()만 오버라이드.

17.10 Class.forName vs ClassLoader.loadClass

메서드 동작 초기화 여부
Class.forName("X") 클래스 로드 + 초기화
loader.loadClass("X") 클래스 로드만 수행 ❌ (사용 시 초기화)
// 초기화 포함
Class.forName("com.example.HelloWorld");

// 초기화 미포함
ClassLoader loader = ...;
loader.loadClass("com.example.HelloWorld");

17.11 이중 위임 모델

  • 요청은 자기 자신부터 시작해 부모로 위임.
  • 부모가 처리할 수 없으면 자신이 로드.
  • 보안 강화: 핵심 클래스(예: java.lang.Object)는 항상 부트스트랩 로더가 로드.
// 예시: java.lang.Object 로드 시 순서
1. 시스템 로더 캐시 검색 → 없음  
2. 부모(확장 로더)에게 위임 → 없음  
3. 부모가 없으므로 부트스트랩 로더에게 위임 → 성공

장점:

  • 중복 로딩 방지.
  • 핵심 클래스 보호.

단점:

  • 상위 로더가 하위 로더의 클래스를 접근할 수 없음.
  • 예: 시스템 클래스에서 애플리케이션 클래스를 생성해야 할 때 문제가 발생.

해결책: 스레드 컨텍스트 클래스 로더(Thread Context ClassLoader) 활용.

17.12 이중 위임 모델의 파괴 사례

  1. 기존 코드 호환성 문제 (JDK 1.2 이전): loadClass()를 오버라이드할 수 있도록 하기 위해 findClass() 도입.
  2. 스레드 컨텍스트 로더 (JNDI, JDBC 등): 상위 로더가 하위 로더의 클래스를 요청.
  3. 동적 모듈 시스템(OSGi): 네트워크 구조로 변경, 각 모듈에 고유 로더 제공.

이러한 "파괴"는 기술적 필요성에 의한 혁신이며, 학습 가치가 큼.

17.13 열거형(핫 스왑) 구현

  • 동일한 클래스명을 다른 로더가 로드하면 서로 다른 타입으로 인식.
  • 이를 활용해 런타임 시 클래스 교체 가능.
  • 예: 웹 서버에서 새로운 배포 파일로 클래스를 다시 로드.
CustomClassLoader newLoader = new CustomClassLoader();
Class<?> updatedClass = newLoader.loadClass("com.app.Service");

17.14 샌드박스 보안 모델

  • 자바의 보안 중심은 샌드박스(Sandbox) 환경.
  • 리소스 접근 제한 (CPU, 메모리, 파일, 네트워크).
  • 버전별 진화:
  • 1.0: 원격 코드는 신뢰할 수 없음.
  • 1.1: 보안 정책 추가.
  • 1.2: 코드 서명 기반 권한 부여.
  • 1.6+: 도메인 기반 접근 제어 (Domain-based Access Control).

17.15 JDK 9 이상의 변화

  • 확장 로더 → 플랫폼 로더(platform class loader).
  • URLClassLoader 상속 제거.
  • 클래스 로더에 이름 부여 (platform, app).
  • 부트스트랩 로더는 내부 구현, 기존 코드 호환 위해 null 반환.
  • 모듈 기반 로딩: 모듈 식별 후 해당 모듈 로더에 위임.
System.out.println(ClassLoader.getPlatformClassLoader()); // platform
System.out.println(ClassLoader.getSystemClassLoader().getName()); // app

태그: java 클래스 로더 이중 위임 모델 커스텀 로더 OSGi

5월 28일 17:36에 게시됨