[Java][JVM] 클래스 로더(Class Loader System) 정리

이영재·2025년 5월 2일
0

JVM

목록 보기
3/5

0. 들어가며

이전에 JVM의 구조와 동작 과정을 정리하면서, 클래스 로더에 대해 더 깊이 알아보고 싶다는 생각이 들었다.
클래스 로더는 JVM이 프로그램을 실행하기 전에 필요한 클래스를 메모리로 불러오는 역할을 한다.

이번 글에서는 클래스 로더 시스템이 무엇인지, 그리고 어떤 방식으로 동작하는지 조금 더 자세히 정리해보려고 한다.

1. 클래스 로더란?

먼저 클래스 로더 시스템이란 바이트코드(.class 파일)를 메모리에 적재하고 연결(Linking)하는 JVM 구성 요소다.

Java 는 동적 로드 방식을 사용한다.
Java 프로그램은 처음부터 모든 클래스를 한꺼번에 메모리에 로드하지 않는다.
필요할 때(on-demand) 해당 클래스를 메모리에 적재하는 지연 로딩(Lazy Loading) 방식을 사용

자바의 클래스 로딩은 클래스 참조 시점에 JVM에 코드가 링크되고, 실제 런타임 시점에 로딩되는 동적 로딩을 거친다.

  • 로딩 : 클래스 정보를 런타임 데이터 영역에 저장하는 것
  • 링크 : 로딩 + 검증/준비/해석

앞서 그림에서도 볼 수 있듯이, 클래스 로더는 내부적으로 Loading, Linking, Initialization이라는 세 가지 과정을 거친다.

단계설명
로딩(Loading).class 파일을 읽어 JVM 메모리에 적재
링크(Linking)메모리에 로드된 클래스들의 참조를 연결하고 검증
초기화(Initialization)static 변수 초기화, static 블록 실행

2. 클래스 로딩의 과정

2.1 로딩(Loading)

클래스 로딩은 JVM이 .class 파일(바이트코드)을 읽어 들여, 메모리(Method Area) 에 저장하는 과정이다.
이때 메모리에 올라가는 것은 "바이트코드"이지, 아직 기계어로 변환된 상태는 아니다.

JVM은 프로그램 실행 흐름에 따라, 필요한 시점에만 클래스를 동적으로 로딩(Lazy Loading)하여 메모리 사용량을 최적화한다.

로딩 단계에서 수행하는 작업

  • .class 파일을 읽어 들인다.
  • 메모리(Method Area)에 클래스 메타데이터를 저장한다. (클래스 이름, 부모 클래스 정보, 필드, 메서드, 인터페이스 등)
  • 아직 바이트코드 형태로 존재하며, 기계어로 변환되지는 않는다.

    클래스 로더는 "기계어 변환"을 하지 않고, 바이트코드를 메모리에 준비만 한다.

로딩 시점

프로그램 시작 시, main() 메서드를 포함한 메인 클래스를 가장 먼저 로드한다.
이후 코드 흐름에 따라

  • new 연산자를 통해 객체를 생성하거나,
  • 메서드를 호출하거나,
  • static 필드를 참조하는 순간 해당 클래스가 필요해지는 시점에 로딩된다.

2.2 링크(Linking)

링크(Linking)는 메모리에 로드된 클래스가 정상적으로 실행될 수 있도록 준비하는 과정이다.

즉, 메모리에 올라간 바이트코드가 JVM 규칙에 맞는지 검증하고,
프로그램이 안정적으로 실행될 수 있도록 필요한 기본 구조를 완성하는 작업이다.

링크는 총 세 단계로 이루어진다
검증(Verification) → 준비(Preparation) → 해석(Resolution)

1. 검증(Verification)

로드된 .class 파일이 자바 언어 명세와 JVM 명세를 만족하는지 검사한다.

검증하는 항목 예시

  • 잘못된 바이트코드가 아닌지
  • 타입 안전성(type safety)이 유지되는지
  • 메서드 호출 규칙을 어기지 않는지

검증을 통과하지 못하면 프로그램은 실행되지 않고,
ClassFormatError, VerifyError 와 같은 오류가 발생해 실행이 중단된다.

검증은 JVM이 프로그램의 안정성과 보안을 확보하기 위해 반드시 거치는 단계다.

2. 준비(Preparation)

클래스가 정의한 static 필드에 대해 메모리를 할당한다.
이때, 필드들은 일단 기본값(0, null, false 등) 으로 초기화된다.

  • 예: private static int counter; → 0으로 초기화

여기서 말하는 초기화는 기본값 세팅만 의미한다.
코드에 직접 작성한 초기값(예: private static int counter = 100;)은 아직 적용되지 않는다. (그건 다음 단계인 초기화(Initialization) 에서 처리된다.)

추가 인사이트

  • 준비(Preparation)는 메모리에 로드된 클래스에 한해서만 진행된다.
  • 아직 사용되지 않은 클래스는 디스크(jar 파일) 안에 남아 있으며, 준비 작업조차 수행되지 않는다.

3. 해석(Resolution)

코드 안에 있는 심볼릭 레퍼런스(Symbolic Reference) 를
실제 메모리 참조(Direct Reference) 로 변환한다.

예를 들어,

  • new User()처럼 다른 클래스를 참조하는 부분에서,
  • User라는 이름(심볼)을 실제 메모리상의 User 클래스 객체 주소로 치환하는 작업이다.

해석(Resolution)은 프로그램 실행 시 바로 수행될 수도 있고, 필요할 때마다 지연(lazy) 수행될 수도 있다.

추가 인사이트

  • 해석 역시 메모리에 올라온 클래스에 대해서만 진행된다.
  • 아직 사용되지 않은 클래스에 대한 심볼 변환은 이루어지지 않는다.

2.3 초기화(Initialization)

초기화(Initialization)는 클래스 로딩과 링크(검증, 준비, 해석)가 끝난 후, static 필드에 우리가 코드에 작성한 초기값을 할당하고, static 블록을 실행하는 과정이다.

즉, 준비(Preparation) 단계에서는 기본값만 세팅되어 있던 static 변수에
실제 개발자가 의도한 값을 적용하는 마지막 설정 작업이다.

  • 이 단계가 끝나야 비로소 클래스를 "완전히 사용할 수 있는 상태"가 된다.
  • 초기화(Initialization)는 메모리에 로드된 클래스에 대해서만 진행된다.

3. JVM의 클래스 로더 종류

JVM은 다양한 클래스를 메모리에 로딩하기 위해 여러 종류의 클래스 로더(Class Loader) 를 사용한다.

클래스 로더는 역할에 따라 계층 구조(Hierarchy)로 동작하며, 클래스를 찾을 때 위쪽(부모)부터 먼저 위임하고, 없으면 아래쪽(자기 자신)이 처리하는 구조다.
이를 "위임 모델(Delegation Model)" 이라고 한다.

3.1 부트스트랩 클래스 로더 (Bootstrap ClassLoader)

  • JVM이 가장 먼저 사용하는 최상위 클래스 로더이다.
  • Java 표준 라이브러리(java.lang., java.util., java.io.* 등)를 메모리에 로드한다.
  • 예를 들어 String, Integer, System 같은 클래스는 부트스트랩 클래스 로더가 로드한다.
  • C, C++로 작성된 네이티브 코드로 동작하기 때문에 우리가 Java 코드로 직접 다룰 수 없다.
  • 메모리상에서는 null로 표현되기도 한다. (getClassLoader()가 null 반환)

    JVM이 정상 동작하려면 필수적인 기본 클래스를 로드하는 역할을 담당한다.

3.2 확장 클래스 로더 (Extension ClassLoader)

  • 부트스트랩 클래스 로더 다음으로 동작하는 클래스 로더다.
  • 기본 Java 기능을 확장하는 추가적인 라이브러리를 메모리에 로드한다.
  • 주로 $JAVA_HOME/lib/ext 디렉터리에 위치한 .jar 파일들을 로드한다.
  • 요즘은 모듈 시스템(JPMS) 도입 이후, 예전만큼 사용되지 않지만 개념상 여전히 존재한다.

    시스템을 확장하거나 보안 관련 기능, 암호화 기능(JCE) 같은 확장 모듈을 로드하는 역할을 한다.

3.3 애플리케이션 클래스 로더 (Application ClassLoader)

  • 우리가 작성한 애플리케이션 코드와,
  • 프로젝트에 추가된 외부 라이브러리(jar 파일) 들을 메모리에 로드한다.
  • 실행 시 설정된 classpath 경로를 기준으로 클래스를 찾는다.
  • 우리가 작성한 대부분의 클래스(src/main/java)는 이 애플리케이션 클래스 로더가 메모리에 올린다.

이미지 상에서

  • System ClassLoader가 바로 이 Application ClassLoader에 해당한다.

실무에서는 가장 많이 사용하는 클래스 로더로, Spring Boot, Hibernate, JPA, MyBatis 같은 라이브러리들도 이 클래스로더를 통해 메모리에 올라온다.

4. 클래스 로더의 작동 원칙

JVM 클래스 로더는 클래스를 로드할 때 "부모 위임 모델(Parent Delegation Model)" 을 따른다.

즉, 클래스를 로드할 때 자신이 직접 로드하는 것이 아니라 먼저 부모의 클래스로더에게 "이 클래스 있나요?" 하고 묻는다.

4.1 작동 순서

  1. 부모 클래스 로더에게 위임 요청 (Bootstrap → Extension → Application 순서로)
  2. 부모가 클래스를 찾으면 → 부모가 로드
  3. 부모가 클래스를 못 찾으면 → 자신이 직접 로드

4.2 왜 이런 방식으로 작동할까?

  • 중복 로딩 방지: 같은 클래스를 여러 번 로드하는 문제를 막는다.
  • 보안성 확보: JVM은 '기본 API'라는 하위 클래스 로더가 함부로 복제하거나 바꿀 수 없게, 부트스트랩 로더에서만 발급하도록 막아버린 것.
  • 일관성 유지: 시스템 전반에서 동일한 클래스를 공유할 수 있다.

"부모를 먼저 믿고 맡긴다(Delegation)"는 개념이 핵심이다.

5. 클래스 로더의 특징

클래스 로더는 JVM 안에서 굉장히 중요한 역할을 하며, 다음과 같은 특징을 가진다.

5.1 지연 로딩 (Lazy Loading)

  • 클래스를 프로그램 시작 시 한꺼번에 다 로딩하지 않는다.
  • 필요한 시점에 (ex: 객체 생성, static 필드 참조 시) 메모리에 로딩한다.
  • 이 덕분에 메모리 사용량을 최적화할 수 있다.

5.2 계층적 구조 (Hierarchy)

  • 클래스 로더는 계층적으로 구성된다.
  • 위쪽 부모 로더가 먼저 시도하고, 없을 때만 아래쪽 로더가 직접 로딩한다.
  • 이 구조 덕분에 클래스를 중복 없이 일관성 있게 관리할 수 있다.

5.3 사용자 정의 가능 (Custom ClassLoader)

  • 개발자가 직접 클래스를 로딩하는 로직을 작성할 수 있다.
  • 주로 플러그인 시스템, 동적 로딩이 필요한 경우에 Custom ClassLoader를 구현한다.

MyClassLoader.java

public class MyClassLoader extends ClassLoader {
    // 사용자 정의 로딩 로직
}
  • JVM이 제공하는 기본 클래스 로더 외에도, 필요에 따라 직접 클래스를 로딩할 수 있다.

6. 마무리

이번 글에서는 JVM의 클래스 로더 시스템을 살펴봤다.

클래스 로더는 단순히 클래스를 읽어들이는 역할을 넘어, 메모리 최적화, 보안성 강화, 프로그램 일관성 유지까지 책임지고 있다.

특히 JVM이 클래스를 로드할 때 부모 위임 모델을 따르고, 필요한 순간에만 클래스를 로드하는 동적 지연 로딩 방식을 사용한다는 점이 핵심이었다.

다음 글에서는 클래스 로더와 JVM 메모리 구조(Runtime Data Area)의 관계,
그리고 클래스 로더를 직접 커스터마이징하는 방법까지 더 깊이 있게 살펴보자.

0개의 댓글