이전에 JVM의 구조와 동작 과정을 정리하면서, 클래스 로더에 대해 더 깊이 알아보고 싶다는 생각이 들었다.
클래스 로더는 JVM이 프로그램을 실행하기 전에 필요한 클래스를 메모리로 불러오는 역할을 한다.
이번 글에서는 클래스 로더 시스템이 무엇인지, 그리고 어떤 방식으로 동작하는지 조금 더 자세히 정리해보려고 한다.
먼저 클래스 로더 시스템이란 바이트코드(.class 파일)를 메모리에 적재하고 연결(Linking)하는 JVM 구성 요소다.
Java 는 동적 로드 방식을 사용한다.
Java 프로그램은 처음부터 모든 클래스를 한꺼번에 메모리에 로드하지 않는다.
필요할 때(on-demand) 해당 클래스를 메모리에 적재하는 지연 로딩(Lazy Loading) 방식을 사용
자바의 클래스 로딩은 클래스 참조 시점에 JVM에 코드가 링크되고, 실제 런타임 시점에 로딩되는 동적 로딩을 거친다.
앞서 그림에서도 볼 수 있듯이, 클래스 로더는 내부적으로 Loading, Linking, Initialization이라는 세 가지 과정을 거친다.
단계 | 설명 |
---|---|
로딩(Loading) | .class 파일을 읽어 JVM 메모리에 적재 |
링크(Linking) | 메모리에 로드된 클래스들의 참조를 연결하고 검증 |
초기화(Initialization) | static 변수 초기화, static 블록 실행 |
클래스 로딩은 JVM이 .class 파일(바이트코드)을 읽어 들여, 메모리(Method Area) 에 저장하는 과정이다.
이때 메모리에 올라가는 것은 "바이트코드"이지, 아직 기계어로 변환된 상태는 아니다.
JVM은 프로그램 실행 흐름에 따라, 필요한 시점에만 클래스를 동적으로 로딩(Lazy Loading)하여 메모리 사용량을 최적화한다.
.class
파일을 읽어 들인다.클래스 로더는 "기계어 변환"을 하지 않고, 바이트코드를 메모리에 준비만 한다.
프로그램 시작 시, main() 메서드를 포함한 메인 클래스를 가장 먼저 로드한다.
이후 코드 흐름에 따라
링크(Linking)는 메모리에 로드된 클래스가 정상적으로 실행될 수 있도록 준비하는 과정이다.
즉, 메모리에 올라간 바이트코드가 JVM 규칙에 맞는지 검증하고,
프로그램이 안정적으로 실행될 수 있도록 필요한 기본 구조를 완성하는 작업이다.
링크는 총 세 단계로 이루어진다
검증(Verification) → 준비(Preparation) → 해석(Resolution)
로드된 .class 파일이 자바 언어 명세와 JVM 명세를 만족하는지 검사한다.
검증하는 항목 예시
검증을 통과하지 못하면 프로그램은 실행되지 않고,
ClassFormatError
, VerifyError
와 같은 오류가 발생해 실행이 중단된다.
검증은 JVM이 프로그램의 안정성과 보안을 확보하기 위해 반드시 거치는 단계다.
클래스가 정의한 static 필드에 대해 메모리를 할당한다.
이때, 필드들은 일단 기본값(0, null, false 등) 으로 초기화된다.
여기서 말하는 초기화는 기본값 세팅만 의미한다.
코드에 직접 작성한 초기값(예: private static int counter = 100;)은 아직 적용되지 않는다. (그건 다음 단계인 초기화(Initialization) 에서 처리된다.)
추가 인사이트
코드 안에 있는 심볼릭 레퍼런스(Symbolic Reference) 를
실제 메모리 참조(Direct Reference) 로 변환한다.
예를 들어,
해석(Resolution)은 프로그램 실행 시 바로 수행될 수도 있고, 필요할 때마다 지연(lazy) 수행될 수도 있다.
추가 인사이트
초기화(Initialization)는 클래스 로딩과 링크(검증, 준비, 해석)가 끝난 후, static 필드에 우리가 코드에 작성한 초기값을 할당하고, static 블록을 실행하는 과정이다.
즉, 준비(Preparation) 단계에서는 기본값만 세팅되어 있던 static 변수에
실제 개발자가 의도한 값을 적용하는 마지막 설정 작업이다.
JVM은 다양한 클래스를 메모리에 로딩하기 위해 여러 종류의 클래스 로더(Class Loader) 를 사용한다.
클래스 로더는 역할에 따라 계층 구조(Hierarchy)로 동작하며, 클래스를 찾을 때 위쪽(부모)부터 먼저 위임하고, 없으면 아래쪽(자기 자신)이 처리하는 구조다.
이를 "위임 모델(Delegation Model)" 이라고 한다.
String
, Integer
, System
같은 클래스는 부트스트랩 클래스 로더가 로드한다.JVM이 정상 동작하려면 필수적인 기본 클래스를 로드하는 역할을 담당한다.
$JAVA_HOME/lib/ext
디렉터리에 위치한 .jar
파일들을 로드한다.시스템을 확장하거나 보안 관련 기능, 암호화 기능(JCE) 같은 확장 모듈을 로드하는 역할을 한다.
src/main/java
)는 이 애플리케이션 클래스 로더가 메모리에 올린다.이미지 상에서
실무에서는 가장 많이 사용하는 클래스 로더로, Spring Boot, Hibernate, JPA, MyBatis 같은 라이브러리들도 이 클래스로더를 통해 메모리에 올라온다.
JVM 클래스 로더는 클래스를 로드할 때 "부모 위임 모델(Parent Delegation Model)" 을 따른다.
즉, 클래스를 로드할 때 자신이 직접 로드하는 것이 아니라 먼저 부모의 클래스로더에게 "이 클래스 있나요?" 하고 묻는다.
"부모를 먼저 믿고 맡긴다(Delegation)"는 개념이 핵심이다.
클래스 로더는 JVM 안에서 굉장히 중요한 역할을 하며, 다음과 같은 특징을 가진다.
MyClassLoader.java
public class MyClassLoader extends ClassLoader {
// 사용자 정의 로딩 로직
}
이번 글에서는 JVM의 클래스 로더 시스템을 살펴봤다.
클래스 로더는 단순히 클래스를 읽어들이는 역할을 넘어, 메모리 최적화, 보안성 강화, 프로그램 일관성 유지까지 책임지고 있다.
특히 JVM이 클래스를 로드할 때 부모 위임 모델을 따르고, 필요한 순간에만 클래스를 로드하는 동적 지연 로딩 방식을 사용한다는 점이 핵심이었다.
다음 글에서는 클래스 로더와 JVM 메모리 구조(Runtime Data Area)의 관계,
그리고 클래스 로더를 직접 커스터마이징하는 방법까지 더 깊이 있게 살펴보자.