목차

     

    배경

     

    지난번 JVM이 바이트코드를 어떻게 실행시키는지에 대해 알아봤다.

    beststar-1.tistory.com/2

     

    JVM(Java Virtual Machine), 바이트코드(Byte Code)

    이 글은 자바 프로그램이 운영체제에 독립적으로 구동되게 해주는 근간이 되는 JVM에 대해 공부하고 정리한 글이며 조금이나마 이해에 도움이 되기를 바란다. 내용이 방대하다고 판단해 여러

    beststar-1.tistory.com

    그렇지만 자바 실행 환경이라는 큰 구조속에서 JVM이 어떤 동작을 하는지 파악을 한 것이고 JVM 내부 구조를 중심으로 들여다보진 않았다. 숲을 봤으니 나무를 하나씩 보자. 먼저 클래스로더를 들여다보자. 

     

    💡 Java 8 을 중심으로 설명하는 점 참고 바란다.

     


    과정

    클래스 로더 시스템 (Class Loader)

    클래스 로더는 자바 바이트 코드를 읽어서 JVM의 실행 엔진이 사용할 수 있도록

    Runtime Data Area(JVM Memory)의 메서드 영역(Method Area)에 적재하는 역할을 한다.

     

    위 그림에 나온 것처럼 클래스 파일이 JVM에 로드될 때 가장 먼저 클래스 로더를 거쳐간다.

    내부 구성요소와 동작 과정을 들여다보자.

    https://dailyheumsi.tistory.com/196

    클래스 로딩 Step 1 - 로딩(Loading)

    .class 파일을 읽어서 바이너리 코드로 만들고 이를 메모리의 메서드 영역(Method Area)에 저장하는 과정이다.

    저장하는 데이터는 다음과 같다.

    • Fully-Quailified Class Name(FQCN)
      • 클래스 로더, 클래스 패키지 경로, 패키지 이름, 클래스 이름을 모두 포함한 값
        예) java.lang.Character$Subset
    • Class, Interface, Enum을 구분하여 저장
    • 메서드와 변수

    로딩이 끝나면 해당 클래스 타입의 객체를 생성하여 메모리의 힙 영역(Heap Area)에 저장한다.


    서로 상하 관계에 있는 클래스 로더들이 정해진 순서에 따라 클래스를 로딩하려는데,

    클래스 로딩을 요청할 때 상위 클래스 방향으로 위임하는 메커니즘이 있다.

     

    💡 이 메커니즘을 Delegation Model(위임 모델)이라고 한다.

    클래스 로더에 클래스 유형별로 계층을 구분하고, 로딩될 때 전부 다 로딩되진 않고 해당 클래스 로더 까지만 부분적으로 탐색하고 로딩할 수 있게 해주는 목적의 모델이다.

     

    아래 그림은 개발자가 직접 작성한 Internal이라는 클래스를 로딩하는 과정의 예시이다.

    https://homoefficio.github.io/2018/10/13/Java-클래스로더-훑어보기

    ClassLoaderRunner에서 loadClass()로 Internal 클래스 로딩을 요청하면,
    Application → Extension → Bootstrap 클래스 로더의 순서대로 로딩 요청을 위임한다.

    이후 기본 라이브러리인 rt.jar → 외부 라이브러리인 ext → JVM이 프로그램을 실행할 때 클래스를 찾기 위한 기준이 되는 경로인 classpath 순으로 클래스 로딩을 시도하고, 그래도 없다면 ClassNotFoundException이 발생하게 된다.

     

    아래는 SampleClassLoader라는 클래스를 java.lang.ClassLoader.loadClass()로 클래스 로딩을 시도했으나 찾지 못한 예시이다. 이어서 메커니즘에 의해 하위 클래스에서 java.net.URLClassLoader.findClass()로 클래스 로딩을 시도했고 최종 하위 클래스까지 도달했으므로 클래스를 못 찾았다는 예외가 발생하는 것이다.

    java.lang.ClassNotFoundException: com.beststar.classloader.SampleClassLoader    
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)    
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)    
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)    
        at java.lang.Class.forName0(Native Method)    
        at java.lang.Class.forName(Class.java:348)

     

    3가지 기본 클래스 로더

    https://homoefficio.github.io/2018/10/13/Java-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A1%9C%EB%8D%94-%ED%9B%91%EC%96%B4%EB%B3%B4%EA%B8%B0/

    BootStrap

    • BootStrap 클래스로더는 3가지 기본 클래스로더 중 최상위 클래스로더로서, 쉽게 말하면 jre/lib/rt.jar에 담긴 JDK 클래스 파일을 로딩한다.
    • Native C로 구현돼 있어서, String.class.getClassLoader()는 그냥 null을 반환한다. Primordial(원시적) ClassLoader라고 불리기도 한다.

    Extension

    • Extension 클래스로더는 jre/lib/ext 폴더나 java.ext.dirs 환경 변수로 지정된 폴더에 있는 클래스 파일을 로딩한다.
    • Java로 구현되어 있으며 sun.misc.Launcher 클래스 안에 static 클래스로 구현되어 있으며, URLClassLoader를 상속하고 있다.
    • Bootstrap 클래스로더를 자기 부모로 설정을 하고 필요할 때 Bootstrap 클래스로더에게 클래스 로딩을 넘겨버린다. 주로 Extension 클래스로더가 클래스 로딩에 실패했을 때 발생한다.

    💡 URLClassLoader는 클래스패스 내부는 물론이고 외부에 있는 클래스까지도 로딩해서 내부에 있는 클래스와 조합 사용까지 가능하게 해주는 클래스이다. 자세히 알고 싶으신 분들을 위해 참고했던 링크를 남겨둔다. 

    Application

    • Application 클래스로더는 -classpath(또는 -cp)나 JAR 파일 안에 있는 Manifest 파일의 Class-Path 속성 값으로 지정된 폴더에 있는 클래스를 로딩한다.
    • Extension 클래스로더와 마찬가지로 Java로 구현되어 있으며, sun.misc.Launcher 클래스 안에 static 클래스로 구현되어 있으며, URLClassLoader를 상속하고 있다.
    • 개발자가 애플리케이션 구동을 위해 직접 작성한 대부분의 클래스는 이 애플리케이션 클래스로더에 의해 로딩된다.
    public void printClassLoaders() throws ClassNotFoundException {
    
        System.out.println("해당 클래스의 클래스 로더 :"
            + PrintClassLoader.class.getClassLoader());
    
        System.out.println("Logging 클래스의 클래스 로더 :"
            + Logging.class.getClassLoader());
    
        System.out.println("ArrayList 클래스의 클래스 로더 :"
            + ArrayList.class.getClassLoader());
    }
    해당 클래스의 클래스 로더 : sun.misc.Launcher$AppClassLoader@18b4aac2
    Logging 클래스의 클래스 로더 : sun.misc.Launcher$ExtClassLoader@3caeaf62
    ArrayList 클래스의 클래스 로더 : null

    위 예제 코드에서 볼 수 있듯
    Applicaton(AppClassLoader), Extension(ExtClassLoader), BootStrap(null) 3가지 클래스로더가 있다.

    PrintClassLoader 클래스의 경우 개발자가 직접 작성한 클래스,

    Logging 클래스는 jre/lib/ext 폴더나 java.ext.dirs 환경 변수로 지정된 폴더에 있는 클래스,

    ArrayList 클래스의 경우 rt.jar에 담긴 클래스이다.

    클래스 로딩 Step 2 - 링크(Linking)

    코드 내부의 레퍼런스를 연결하는 과정이다.

     

    Verify(검증)

    .class 파일 형식이 유효한지 검사한다.

    Prepare(준비)

    클래스가 필요로 하는 메모리를 할당하고 클래스의 필드, 메서드, 인터페이스를 나타내는 데이터 구조를 준비한다.

    Resolve (분석, Optional)

    클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 실제 메모리 레퍼런스로 교체한다.
    Optional인 이유는 이때 교체(binding) 될 수도 있고 이후 사용이 일어날 때에 동적으로 교체될 수도 있기 때문이다.

    💡 심볼릭 레퍼런스는 메모리 번지의 참조를 의미하는 것이 아니라 이름에 의한 참조를 의미하는 것이다.
    '바로가기' 파일을 떠올린다면 이해가 쉬울 것 같다.

    클래스 로딩 Step 3 - 초기화(Initialization)

    static 변수를 초기화하고 값을 할당하는 과정이다.

     

     

    지금까지의 내용을 간략화하면 아래와 같다.

     

    클래스 로더 시스템은 바이트 코드를 BootStrap, Extension, Application 3가지의 클래스 로더로 로딩하여
    링크로 코드 내부의 레퍼런스를 연결하고 추가적으로 static 변수는 초기화 과정까지 거쳐

    실행 엔진이 사용할 수 있도록 JVM 메모리의 메서드 영역에 적재하는 시스템이다.

     


     

    결과

    JVM의 클래스 로더와 관련하여 아래 사항들을 공부하고 정리했다.

    • 클래스 로더 정의
    • 클래스 로더 구조
    • 클래스 로딩 메커니즘

    성과

    • 이 글은 2020년 12월에 처음 포스팅한 JVM글에서 이번에 따로 분리해낸 것인데|
      분리하기 전에는 머릿속에만 deep-dive 해둔 게 남아있다 보니 정리가 안된 상태였다.
      분리하면서 설명이 부족한 부분이 더욱 명확히 보이기 시작했고 공부와 글 정리 효율 또한 향상되는 것을 느끼는 계기가 됐다.

     


    출처

    JVM 구조

    https://catch-me-java.tistory.com/12

     

    클래스 로더 요약 설명

    https://inor.tistory.com/4

     

    3가지 기본 클래스로더

    https://homoefficio.github.io/2018/10/13/Java-클래스로더-훑어보기/

     

    클래스 로딩 코드 예제

    https://www.baeldung.com/java-classloaders

     

    클래스 로더 - 로딩, 링크, 초기화
    https://dailyheumsi.tistory.com/196