Java ClassLoader

ClassLoader란?

JVM 는 클래스를 로드하는데 Class Loader라고 하는 것을 사용하고 있다. Class Loader는 하나가 아니고, JVM 가 사용별로 여러개의 클래스 로더들 외에 J2EE 컨테이너에서는 여러개 클래스 로더가 계층적으로 추가 정의되고 있고, 유저가 독자적으로 정의하는 것도 가능하다.

Class Loader는 클래스를 메모리상에 로드를 한다. 다시 말해, 컴파일된 클래스(.class)를 동적으로 메모리에 로딩하는 역할을 한다. 이때 동적으로 로딩한다는 것은 컴파일 타임이 아닌 런타임 때 클래스 파일이 필요하여 실행할 경우 메모리에 올린다는 것이다.

ClassLoader의 기본 동작

J2SE에 정의된 클래스 로더는 다음 3가지가 있다.

  • 부트스트랩 클래스 로더 (Bootstrap Class Loader)
    • 초기 클래스 로더
    • 코어 패키지 등(rt.jar, i18n.jar) 클래스 로더
  • 확장 클래스 로더 (Extension Class Loader)
    • 확장 기능의 설치형 옵션 패키지(lib/ext)의 클래스 로더

    • Java 8 까지: URLClassLoader 를 상속하며, jre/lib/ext 및 java.ext.dirs 환경 변수로 지정된 폴더 내 클래스들을 로드한다.

    • Java 9 이후: jre/lib/ext 와 java.ext.dirs 를 지원하지 않고, Java SE 의 모든 클래스 와 JCP(Java Community Process)에 의해 표준화된 모듈의 클래스 를 로드한다.

      • BuiltinClassLoader 를 상속하여 ClassLoader 의 내부 static 클래스로 구현되었으며, PlatformClassLoader 로 변경되었다
  • 사용자 정의 클래스 로더(System Class Loader, Aplication Class Loader)
    • classpath 클래스 로더

클래스 로더 간에는 부모와 자식 관계가 있으며, 부모 클래스 로더는 자식 클래스 로더를 알지 못하지만 자식 클래스 로더는 부모를 알고 있다. 자식 클래스 로더에 클래스 로딩 요청이 들어오면 부모에게 위임하여 가장 먼저 발견한 클래스를 로딩한다. 만약 대상 클래스가 요청을 받은 클래스 로더보다 자식 클래스로더의 책임 범위에 있다면 해당 클래스는 발견되지 않고 로딩에 실패한다.

  1. 클래스 로더가 클래스 로딩을 요청 받는다. loadClass()
  2. 이미 로드되어 있는지 확인한다. findLoadedClass
  3. 로드에 없다면, 부모 클래스 로더(없는 경우 부트스트랩 클래스 로더findBootstrapClassOrNull())에 처리를 위임(delegation)한다. parent.loadClass(name, false)
  4. 로드되지 않았다면 찾아서 로드 시도. findClass()

구체적으로 java.lang.ClassLoaderloadClass(..) 메소드를 보면, 위 내용을 확인할 수 있다.

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

부트스트랩 클래스 로더는 코어 패키지 등을 로드하는 클래스 로더로, 그 자체로는 부모를 갖지 않는다. 사용자 정의 클래스 로더는 부팅 시 실행되는 시스템 클래스 로더를 부모로 파생한다.

예를 들어, J2EE 컨테이너의 경우 EAR의 유틸리티 JAR을 로드하는 클래스 로더는 WAR의 웹 컴포넌트를 로드하는 클래스 로더가 되므로 웹 컴포넌트는 EAR의 유틸리티 JAR을 호출할 수 있다. 반대로 EAR의 유틸리티 JAR은 자식인 WAR 내의 웹 컴포넌트를 호출할 수 없다.

또한, static으로 한정된 필드는 클래스 로딩 시 한 번만 실행되므로 클래스 로더 내에서 하나가 된다. 클래스 로더를 통합하면 유틸리티 클래스를 공통 클래스로 만들 수 있지만, 예상치 못한 부작용이 발생할 수 있으므로 동작을 이해하고 잘 살펴봐야 한다.

클래스 로더는 추상 클래스 java.lang.ClassLoader의 상속된 클래스이다. 예를 들어, 코어 패키지 내에서는 java.security.SecureClassLoaderjava.net.URLClassLoader가 이 클래스의 상속된 클래스로 정의되어 있다.

ClassLoader 검색하는 예제

다음 코드는 클래스 로더를 검색하는 예제입니다.

package com.devkuma.basic.classloader;

class GetClassLoader {
    public static void main(String[] args) {
        // Bootstrap class loader
        System.out.println("- Bootstrap Class Loader");
        ClassLoader bootstrap = "".getClass().getClassLoader();
        System.out.println(bootstrap);
        if (bootstrap != null) {
            ClassLoader parent = bootstrap.getParent();
            System.out.println(parent);
        } else {
            System.out.println("No parent");
        }

        // Platform Class Loader
        System.out.println("- Platform Class Loader");
        ClassLoader extensions = GetClassLoader.class.getClassLoader().getParent();
        System.out.println(extensions);
        if (extensions != null) {
            ClassLoader parent = extensions.getParent();
            System.out.println(parent);
        } else {
            System.out.println("No parent");
        }

        // System Class Loader
        System.out.println("- System Class Loader");
        DemoClass myObj = new DemoClass();
        ClassLoader systems = myObj.getClass().getClassLoader();
        System.out.println(systems);
        if (systems != null) {
            ClassLoader parent = systems.getParent();
            System.out.println(parent);
        } else {
            System.out.println("No parent");
        }

        // Context Classloader
        System.out.println("- Context Classloader");
        ClassLoader threads = Thread.currentThread().getContextClassLoader();
        System.out.println(threads);
        if (threads != null) {
            ClassLoader parent = threads.getParent();
            System.out.println(parent);
        } else {
            System.out.println("No parent");
        }
    }
}

class DemoClass {
    String getClassName() {
        return "DemoClass";
    }
}

다음과 같이 출력된다. 여기서 컨텍스트 클래스 로더는 해당 스레드 내에서 클래스를 로드하는데 사용되는 클래스 로더이다.

- Bootstrap Class Loader
null
No parent
- Platform Class Loader
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7dc5e7b4
null
- System Class Loader
jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7dc5e7b4
- Context Classloader
jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7dc5e7b4



최종 수정 : 2024-02-12