JVM Back to the Basics - Class Loaders

4 minute read

이전 글에서는 JVM의 구조와 그것의 구성요소 중 하나인 Class Loader sub-system의 역할을 알아보았다.

이번 글에서는 클래스 로더(class Loader)의 종류에 대해 알아보자.

개요

Java에서 동적 로딩을 가능하게 해주는 메커니즘은 클래스 로더인데, 이는 Java dynamism의 토대 중 하나이다.

클래스 로더는 실행 중인 Java 환경에 클래스들을 언제, 그리고 어떻게 추가될 수 있는지를 결정해주고, Java 런타임 환경의 중요한 부분들이 악의적인 코드로 대체되지 않도록 해준다. 신뢰하지 못하는 외부 코드를 실행하기 전, 디지털 시그니처를 확인하는 커스텀 클래스 로더를 작성할 수도 있다.

추상적인 JVM 클래스 로더 설계

Bill Venners의 Inside the Java Virtual Machine은 Java 2의 1.2 버전 부터 JVM에는 두 개의 종류의 클래스 로더가 있다고 한다: bootstrap class loaderuser-defined class loaders. 여기서 bootstrap 클래스 로더는 가상 머신 구현체의 일부분이고 user-defined 클래스 로더들은 실행 중인 Java 애플리케이션의 일부분이다. 서로 다른 클래스 로더에 의해 로딩된 클래스들은 JVM안에서 각기 다른 name space(네임 스페이스) 안에 저장된다.

클래스 로더 서브시스템은 java.lang 라이브러리의 여러 클래스와 관련되어 있다. 예를 들어, user-defined 클래스 로더는 java.lang.ClassLoader로부터 상속 받는 일반 Java 객체이다. ClassLoader 클래스의 메소드들은 Java 애플리케이션이 가상 머신의 클래스 로딩 기능에 접근할 수 있게 해준다. 또한, JVM은 로딩하는 모든 타입에 대해, 그 타입을 나타내기 위해 java.lang.Class 클래스의 인스턴스를 생성한다. 모든 객체들 처럼, user-defined 클래스 로더와 Class 클래스의 인스턴스들은 힙에 올라간다. 로딩된 타입에 대한 데이터는 메소드 구역에 올라간다.

The Bootstrap Class Loader

JVM 구현체는 바이너리 파일에 저장된 Java 클래스 파일 형식에 부합하는 클래스 및 인터페이스를 인식할 수 있어야 한다. 구현체는 클래스 파일 외의 다른 형태의 바이너리 또한 인식해도 상관 없으나, 클래스 파일은 반드시 인식할 수 있어야 한다.

모든 JVM 구현체는 bootstrap 클래스 로더가 있다. Bootstrap 클래스 로더는 Java API를 포함하는 신뢰하는 클래스들을 로딩하는 일을 맡는다. JVM specification은 bootstrap 클래스 로더가 어떻게 클래스를 찾을지에 대한 방법은 정의하고 있지 않다. 이는 구현체 설계자의 결정에 맏기는 부분이다.

Fully qualified 타입 이름이 주어졌을 때, bootstrap 클래스 로더는 어떠한 방식으로라도 타입을 정의하는 데이터를 찾아내야 한다. JVM 구현체 중 Sun의 1.1 JDK

전형적인 디폴트 클래스 로더 계층

JVM의 class loader subsystem에는 다음의 세 가지 기본 클래스 로더가 있다고 말한다.

  1. Bootstrap Class Loader or primordial Class Loader
  2. Extension Class Loader
  3. Application Class Loader

위 설계를 생각해보면, 사실상 extension class loader와 application class loader는 Java 언어로 구현된 user-defined 클래스 로더에 해당이 되는 것이다.

Bootstrap Class Loader

  • Core Java API 클래스들을 로딩해준다. i.e. rt.jar (jdk\jre\lib\rt.jar)에 있는 클래스들
  • 이 위치를 bootstrap class path라고 부른다. i.e. Bootstrap 클래스 로더는 bootstrap class path에 있는 클래스들을 로딩해준다.
  • Bootstrap 클래스 로더는 디폴트로 모든 JVM에서 가용하다.
  • Bootstrap 클래스 로더는 Java 언어가 아닌 네이티브 언어인 C/C++ 언어로 구현되었다.

Extension Class Loader

  • Bootstrap 클래스 로더의 자식 클래스이다.
  • Extension class path (jdk\jre\lib\ext)에 있는 클래스들을 로딩해준다.
  • Java 언어로 구현되었고, 이것의 .class 파일은 sun.misc.Launcher$ExtClassLoader.class이다.

Application Class Loader / System Class Loader

  • Extension 클래스 로더의 자식 클래스이다.
  • Application class path로부터 클래스들을 로딩해준다.
  • 내부적으로 환경 변수 class path를 사용한다.
  • Java 언어로 구현되었고, 이것의 .class 파일은 sun.misc.Launcher$AppClassLoader.class이다.

클래스 로딩 과정은 delegation hierarchy 알고리즘을 따른다.

class-loader-hierarchy

단계별로 보면 다음과 같다.

  • JVM이 특정한 클래스를 마주하면, 먼저 .class 파일이 로딩되었는지를 확인한다.
  • 만약 method area로 이미 로딩되었다면, JVM은 로딩된 클래스로 계속 진행한다.
  • 만약 로딩되지 않았다면, JVM은 Class Loader sub-system에게 해당 클래스를 로딩하라고 요청을 보낸다.
  • Class Loader sub-system은 그 요청을 Application 클래스 로더에게 포워딩한다.
  • Application 클래스 로더는 그 요청을 Extension 클래스 로더에게 delegate(위임)하고, Extension 클래스 로더는 또 한 번 Bootstrap 클래스 로더에게 delegate한다.
  • Bootstrap 클래스 로더는 Bootstrap class path를 탐색하여 해당 클래스가 있으면, 그 .class 파일을 로딩한다.
  • 만약 .class 파일이 없다면, Bootstrap 클래스 로더는 요청을 Extension 클래스 로더에게 다시 delegate한다.
  • Extension 클래스 로더는 Extension class path에서 해당 클래스를 탐색하고, 있다면 로딩하고, 없다면 Application 클래스 로더에게 요청을 delegate한다.
  • Application 클래스 로더 또한 Application class path에서 클래스를 탐색하고, 있다면 로딩하고, 없다면 ClassNotFoundException의 런타임 exception이 발생한다.

이러한 단계를 거치는 알고리즘이 Delegation Hierarchy Algorithm이다.

NOTE: 자식 클래스 로더에의해 로딩된 클래스들은 부모 클래스 로더에의해 로딩된 클래스들에 접근할 수 있다. 즉, Application 클래스 로더에의해 로딩된 클래스들은 Extension/Bootstrap 클래스 로더에의해 로딩된 클래스들에 접근할 수 있는 것이다.

예시를 하나 작성해보자.

Student.javaSchool.java 파일을 생성하여 컴파일하는데, School.class 파일은 Application Classpath와 Extension Classpath에 한 카피씩 존재한다고 가정하자.

그리고 다음과 같은 코드를 실행하면 어떤 결과가 나올까?

public class Student {
    public static void main(String[] args) {
        
        System.out.println(String.class.getClassLoader());
        System.out.println(Student.class.getClassLoader());
        System.out.println(School.class.getClassLoader());
    }
}
$> javac Student.java
$> java Student

로 실행을 해보면,

Output:

null
sun.misc.Launcher$AppClassLoader@1985a96
sun.misc.Launcher$ExtClassLoader@1085a85

이런 결과가 나온다.

String.class는 Bootstrap 클래스 로더에의해 로딩되고, Bootstrap 클래스 로더는 Java로 구현되지 않았기 때문에 객체가 생성되지 않아 null이 된다.

하지만, 나머지 클래스 로더들은 Java 객체로써 클래스이름@hashcodeInHexadecimalForm으로 출력된다.

또, Delegation Hierarchy Algorithm에 의해 Extension classpath가 Application classpath보다 더 높은 우선 순위를 가지기 때문에, School.class는 Extension 클래스 로더에의해 먼저 로딩된다.

Customizing Class Loaders

이러한 디폴트 클래스 로딩 메커니즘은 클래스가 한 번 method area로 로딩되고, 만약 .class 파일이 외부에서 변경되어도 이미 로딩된 .class 파일을 사용할 것이다. 만약 서버 애플리케이션을 중단하고 재시작하지않고 (그럴 경우 변경된 클래스 파일이 적용되겠지만), 실시간으로 업데이트된 .class 파일을 적용해줘야할 경우, 커스터마이징된 클래스 로더를 만드는 것도 하나의 방법이다. 다음과 같이 만들 수 있을 것이다.

public class CustomizedClassLoader extends ClassLoader {
    public Class loadClass(String cname) throws ClassNotFoundException {
        // Check whether updated version of class is available or not.
        // If it is available, then load the updated version and return the corresponding class "Class" object.
        // Otherwise return Class object of already loaded .class
    }

    class CustomClassLoaderTest {
        public static void main(String[] args) {
            Student s1 = new Student(); // Default class loader loads Student.class

            CustomizedClassLoader c = new CustomizedClassLoader();
            c.loadClass("Student"); // Customized class loader checks for updates and loads updated version of Student class
            ...
            c.loadClass("Student"); // Customized class loader checks for updates and loads updated version of Student class
        }
    }
}

출처

https://www.artima.com/insidejvm/ed2/jvm4.html

https://www.developer.com/java/java-class-loading-the-basics/

Leave a comment