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 클래스 로더의 필요성
개발자에게는 직접 클래스 로더를 다룰 일이 드물지만, 다음과 같은 이유로 이해가 필수적이다:
ClassNotFoundException나NoClassDefFoundError발생 시 문제 진단이 가능해짐.- 런타임 시 동적 로딩 또는 바이트코드 암호화/복호화가 필요할 때 활용 가능.
- 사용자 정의 로직을 구현하기 위해 커스텀 클래스 로더를 작성할 수 있음.
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 이중 위임 모델의 파괴 사례
- 기존 코드 호환성 문제 (JDK 1.2 이전):
loadClass()를 오버라이드할 수 있도록 하기 위해findClass()도입. - 스레드 컨텍스트 로더 (JNDI, JDBC 등): 상위 로더가 하위 로더의 클래스를 요청.
- 동적 모듈 시스템(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