JVM - ClassLoader

유정현·2024년 3월 27일

개요

JAVA의 클래스로더는 JVM이 작동되는데 있어 아주 중요한 역할을 한다. 이전에 이야기했던 JVM의 대략적 설명을 보면 Compiler는 바이트 코드 단위로 우리의 코드를 JVM에 전달한다. ExecutionEngine이 이 바이트 코드를 각자 읽으면, ( ExecutionEngine 정리) 우리가 작성한 코드들이 실행된다. 이때 필요한 클래스를 로드해주는 존재가 클래스로더다.

클래스 로더의 중요성

컴파일러를 통해 바이트코드 단위로 변경된 즉 클래스파일들은 클래스패스 내 디렉토리, JAR 파일 내부, 웹 애플리케이션 WAR 파일 내부 등에 있다. 네트워크에 있을 때도 있다. 이런 파일들을 클래스로더는 JVM이 필요하다고 하는 시점에 동적으로 로드해준다. 이 부분이 가장 재밌는 부분이다.

세 가지의 클래스 로더

BootStrapClassLoader (jre/lib/rt.jar)

  1. 총 세 가지의 기본 클래스로더 중 최상위 클래스로더이다. jre/lib/rt.jar에 담긴 JDK 클래스 파일을 로딩한다. 자바 클래스를 로드할 수 있는 자바 자체의 클래스로더와 최소한의 자바 클래스만을 로드한다.
  2. NativeC로 구현돼 있어 getClassLoader() 메소드 실행시 Null을 반환한다.
  3. 버전에 따른 차이
    • Java8
      jre/lib/rt.jar 및 기타 핵심 라이브러리와 같은 JDK의 내부 클래스를 로드한다. ( rt.jar 내에는 java.lang, java.util 등의 필수 패키지들이 있다. )
    • java9 이후
      더 이상 /re.jar이 존재하지 않으며, /lib 내에 모듈화되어 포함됐다. 이제는 정확하게 ClassLoader 내 최상위 클래스들만 로드한다.

Platform ClassLoader (Extension ClassLoader in Java 8)

  1. 부트스트랩 클래스로더를 부모로 갖는 클래스로더로써, 확장 자바 클래스들을 로드한다.
  2. java.ext.dirs 환경변수에 설정된 디렉토리의 클래스 파일을 로드하고, 이 값이 설정되지 않은 경우 ${JAVA_HOME}/jre/lib/ext에 있는 클래스 파일을 로드한다.
  3. 버전에 따른 차이
    • java8
      URLClassLoader를 상속하며, jre/lib/ext 내 모든 클래스를 로드한다.
    • java9 이후
      PlatformLoader로 변경되었다. URLClassLoader가 아닌 BuiltinClassLoader를 상속한다. InnerStatic 클래스로 구현돼 있다.

System ClassLoader (Application ClassLoader in Java 8)

  1. 애플리케이션 레벨에 있는 모든 클래스들을 JVM에 로딩한다. 즉, 사용자가 작성한 코드들을 로딩한다.
  2. Platform ClassLoader의 하위로, classpath 환경 변수에 있는 파일들, -classpath(or -cp) 에 있는 파일들을 로드한다.
  3. JAVA 9 이전엔 ApplicationClassLoader였으며, 기존에는 URLClassLoader를 상속하였지만, 현재 JAVA9 이후에는 BuiltinClassLoader를 상속, 내부 static 클래스로 구현되었다.

ClassLoader의 법칙

ClassLoader는 여러가지이다. 위에서 나온 이야기들만 봐도, 기본적인 세 가지 클래스로더와 이것들이 각각 상속받는 클래스로더들 등등 여러가지가 더 있다. 이렇게 보면 클래스 로더가 그냥 클래스 로딩해주는 것 정도이긴 하지만, 이 안에 내부적으로 더 세세한 규칙들이 있다. 위임 법칙, 가시성 법칙, 유일성 법칙 등등이 있다.

클래스 로더의 세 가지 원칙

DelegationPrinciple(위임 법칙)
VisibilityPrinciple(가시성 법칙)
UniquenessPrinciple(유일성 법칙)

DelegationPrinciple(위임법칙)

위임 원칙은 클래스 로딩이 필요할 때 3가지 기본 클래스로더의 윗 방향으로 클래스 로딩을 위임하는 것을 말한다. 제일 먼저 찾기 시작하는 클래스로더는 Bootstrap ClassLoader이다. 해당 Classloader를 시작으로 하위로 한 단계씩 내려가면서 찾는다. 자바의 상속과 약간 비슷한 느낌이 들긴 하지만, 목적이 아예 다르고 사용하는 것도 아예 다르니 굳이 비교하지 말고 각각으로 이해하는 게 좋을 것 같다.

Visibility Principle(가시성 법칙)

가시범위 원칙은 하위 클래스로더는 상위 클래스 로더가 로딩한 클래스를 볼 수 있지만, 상위 클래스로더는 하위 클래스로더가 로딩한 클래스를 볼 수 없다는 원칙이다. 이렇게 해야 하위 클래스에서 Object에 접근하는 것이 가능하기 때문이다. 역시 객체지향이다 싶었다. 자바의 객체지향에서 상속은 하위는 super()를 통해 상속받은 부모의 내부를 볼 수 있지만, 부모 클래스는 하위 클래스를 볼 수 없다. 이런 내용이 ClassLoader에서도 영향을 준게 아닐까 싶다.

Uniqueness Principle(유일성 법칙)

유일성 법칙은 상위 클래스로더에서 로드한 클래스를 하위 클래스로더에서 로드하면 안된다는 법칙이다. 클래스를 로드하는 일은 가능한한 상위 클래스로더에게 위임하는 것이 이 유일성 법칙과도 연결되어 있는듯 싶다.

세 가지 법칙은 왜 있는가

각 법칙을 잘 보면 어느정도 이해가 간다. 우리가 사용하는 래퍼 클래스, 사용자 정의 클래스, 외부 API에서 의존성 등록해 둔 클래스 그리고 그 내부적으로 사용되는 또 다른 클래스들이 셀 수 없이 많다. 상속 관계들도 생각해보면 쉽지 않다. 그렇기에 이런 법칙들이 없으면 지금의 자바는 없지 않았을까 싶다.

클래스로더의 로딩


클래스 로더가 클래스를 로드하는 과정은 크게 보면 세 가지 과정을 거친다.
Loading, Link, Initializatio 등 이런 과정을 통해서 클래스는 로드된다.

  1. 로딩 과정에서 클래스 로더들이 필요한 클래스들이 로드된다.
  2. Link 과정에서는 검증과 준비하는 과정을 가진다.
  3. Link과정 내부
  • verify는 바이너리 데이터가 유효한지 확인한다. class파일 형식이 유효한지, 여러가지를 체크한 다음 믿을 수 있는 파일인 경우에 진행한다.
  • Prepare는 클래스의 static 변수와 원시타입 변수에 필요한 메모리 공간을 준비한다.
  • Resolution은 선택적으로 진행되는 과정으로 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 힙 메모리 영역에 있는 인스턴스에 대한 레퍼런스로 교체해준다. 즉, 실제 주소값으로 변경해주는 작업을 한다.
  1. 이런 일련의 과정을 통해 우리의 인스턴스가 생성되고 사용하는 것이다.

마무리

약 14~5개월 전 쯤 정리한 자료들을 보면서 추가할 내용 추가하며 정리했다. 다음 JVM 관련 글은 아마 ExecutionEngine이 될 것 같다. 그리고 차후에 실제 예제 코드도 작성해보며 공부해야겠다.

profile
코딩하는 감자입니다.

0개의 댓글