개요

JVM 관점에서 클래스(Class)는 컴파일 단위로, 개별적으로 컴파일되고 로드될 수 있는 가장 작은 코드조각이다.
JVM에서 클래스들을 어떻게 탐색하고 로드하는지 궁금해서 이번 포스팅에서 알아보고자 한다.

JVM의 클래스 로딩 과정

다음과 같은 a.java 파일이 있다고 가정하자.

1
2
3
4
5
6
7
public class A { 
   public static void main(String[] args) {
      B b = new B();
      int i = b.increase(0); 
      System.out.println(i); 
   } 
}

위 a.java 파일이 javac(java compiler)에 의해 a.class 파일로 컴파일되고 실행되면, 애플리케이션의 진입점인 클래스 A의 main() 메서드가 실행된다.

위 main()메서드가 정상적으로 실행되기 위해서는 클래스 B와 같은 다양한 클래스가 로드되야한다.
일반적으로 JVM은 new B()에 해당하는 바이트 코드를 마주쳤을 때, 그때 클래스 B를 찾고 로드하게 된다.

JVM은 클래스와 인터페이스를 동적으로 로드, 링크 및 초기화하기 때문이다.


JVM은 클래스를 로드하기 위해 ClassLoader 객체를 사용한다. 이미 로드된 클래스들은 ClassLoader에 대한 참조를 가지고 있으며, 이 ClassLoader는 해당 클래스에서 사용되는 모든 클래스를 메모리에 로드하는데 사용된다.
따라서, 위 예시에서 로딩 클래스 B는 A.class.getClassLoader().loadClass(“B”)로 변환될 수 있다.

그럼 JDK 내부에 있는 모든 클래스들(ex. java.lang.Object, java.lang.ClassLoader 등)을 로드하는 첫 ClassLoader는 무엇이며 어떻게 생성될까? ClassLoader를 조금 더 살펴보자.

ClassLoader의 계층 구조

JDK 11을 뜯어본 결과, 3가지 ClassLoader가 존재했다. (명칭은 다를 수 있으나 역할은 유사하다)

위 사진처럼 ClassLoader는 부모를 가지고 있는 트리 계층 구조로, 책임이 분리된 형태이다.
각 ClassLoader의 역할을 살펴보자.

- Bootstrap ClassLoader (BootClassLoader)

  • 최상위 ClassLoader로, JVM을 실행하는데 필요한 최소한의 필수 클래스(java.lang 패키지 클래스, Java primitives 클래스 등)를 로드한다.
  • 네이티브 플랫폼에 종속된 코드로 구현되어있다.

- Extensions ClassLoader (PlatformClassLoader)

  • Bootstrap ClassLoader의 자식으로, JDK 확장 디렉토리(보통 $JAVA_HOME/lib/ext 또는 java.ext.dirs)에서 로드된다.
  • 로드 되는 클래스는 locales, security provider 등과 같이 시스템별 configuration을 지정하는데 사용된다.

- System ClassLoader (AppClassLoader)

  • 지정된 클래스 경로에 위치한 유저 클래스를 로드하는 클래스로더이다.
  • Extensions ClassLoaderSystem ClassLoader는 모두 URLClassLoader를 상속받아 만들어진 클래스로더이다.


그럼 이러한 ClassLoader가 어떤 흐름으로 동작하는지 살펴보자.

ClassLoader의 3가지 원칙

위에서 소개한 ClassLoader는 다음 3가지 원칙에 의해 작동된다.

- Delegation Principle (위임 원칙)

  • 클래스를 로드하는 요청이 있으면 ClassLoader는 상위 ClassLoader로 요청을 위임한다.
  • 상위 클래스로더가 해당 클래스 로드에 실패한 경우에만 해당 ClassLoader가 클래스를 로드한다.
  • 만약 최하위 ClassLoader도 해당 클래스를 로드하지 못하면, ClassNotFoundException 또는 NoClassDefFoundError를 던진다.

- Visibility Principle (가시성 원칙)

  • 하위 ClassLoader는 상위 ClassLoader에 의해 로드된 클래스를 확인할 수 있지만, 상위 ClassLoader는 하위 ClassLoader가 로드한 클래스를 확인할 수 없다.

    ClassLoader 간 의존성을 관리하고 격리하기 위한 원칙으로 볼 수 있다.

- Uniqueness Principle (유일성 원칙)

  • 상위 ClassLoader에 의해 이미 로드된 클래스는 하위 ClassLoader에 의해 다시 로드되지 않는다.

    항상 클래스 로드 요청을 상위 ClassLoader로 위임하기 때문에 고유한 클래스를 보장할 수 있게된다.


결국 JVM은 클래스를 로드할 때, 런타임 환경에서 해당 클래스를 나타내는 Class 객체를 만든다.
한 시스템에서 클래스는 “패키지명을 포함한 클래스 명”“자신을 로드한 ClassLoader” 두 가지 정보로 식별된다.

참고

댓글남기기