[JVM] Class Loader

2024. 4. 18. 23:51자바

Class Loader란 ?

Java는 Runtime동안 필요한 클래스 파일을 동적으로 읽어오는데, Class Loader는 여기서 Java 클래스를 JVM에 동적으로 로드하는 역할을 합니다. 

JVM이 동작하다가 필요한 순간(클래스 파일을 참조하는 순간)에 클래스 파일을 읽어 메모리에 동적으로 로드합니다.

Class Loader 덕분에 JVM은 Java 프로그램을 실행하기 위한 정보들을 모두 가지고 있을 필요가 없습니다.

 

 

 

내장 Class Loader의 종류

Bootstrap Class Loader

모든 Java 클래스는 Java.lang.ClassLoader에 의해서 로드됩니다. 하지만 ClassLoader 자체도 클래스입니다.

ClassLoader 자체도 클래스라면 ClassLoader는 누가 로드할까 ?라는 의문이 생길 수 있습니다. 

어딘가에서 로드해서 ClassLoader가 작동을 해야하기 때문입니다.
이러한 ClassLoader를 로드하는 것이 Bootstrap Class Loader입니다.

  • Bootstrap Class Loader는 $Java_HOME/jre/lib 에 있는 rt.jar 및 기타 핵심 라이브러리와 같은 JDK 내부의 클래스를 로드하는 역할(Java 8 기준)
  • Bootstrap Class Loader는 모든 ClassLoader 인스턴스의 부모 역할
  • Bootstrap Class Loader는 Java가 아닌 네이티브 언어(Native C)로 작성
  • 우리가 흔히 사용하는 java.lang 패키지 안에 있는 클래스들을 로드 (ex. java.lang.Object, java.lang.Classloader)

Extension Class Loader (Java 8 기준이름)

  • Extension Class Loader는 Bootstrap Class Loader의 자식
  • 일반적으로 $JAVA_HOME/jre/lib/ext 폴더나 java.ext.dirs 환경 변수로 지정된 폴더에 있는 클래스 파일을 로딩
    (Java 8 기준)
  • URLClassLoader를 상속(Java 8 기준)
  • Bootstrap Class Loader와 다르게 Java로 구현

Application Class Loader (Java 8 기준이름)

  • Extension Class Loader의 자식
  • 지정된 class path에 있는 클래스들을 로딩
  • sun.misc.Launcher 클래스 안에 static클래스로 구현되어 있으며, URLClassLoader를 상속(Java 8 기준)

 

 

 

Java 9 이상 버전에서의 내장 Class Loader

위의 내용에서 Java 8 기준이라고 적힌 내용을 볼 수 있습니다.
Java 9 버전 이상부터는 모듈 시스템의 도입에 맞춰 이름과 범위 구현 내용 등이 바뀌었습니다.

rt.jar, tools.jar가 제거됨

 

rt.jar, tools.jar와 기타 다양한 내부 JAR파일에 저장된 클래스 및 리소스 파일은 보다 효율적인 형식으로 lib폴더 안에 저장됩니다.
이에 따라 rt.jar내의 클래스를 로딩하던 Bootstrap Class Loader가 로딩할 수 있는 범위가 축소되었습니다.


jre/lib/ext, java.ext.dirs, lib/endorsed, java.endorsed.dirs가 제거됨

 

위에 언급한 부분이 제거됨에 따라 jre/lib/ext, lib/endorsed가 파일 시스템에 존재하거나 java.ext.dirs, java.endorsed.dirs가 환경변수로 설정되어 있다면 javac나 java는 종료됩니다.


변경내용 정리

Java 8 Java 9 변경 내용
Bootstrap
ClassLoader
변경 되지 않음 rt.jar 등이 없어짐에 따라 로딩할 수 있는 클래스의 범위가 전반적으로 축소
Extension
ClassLoader
Platform
ClassLoader
1. jre/lib/ext, java.ext.dirs를 지원하지 않음.
2. Java SE의 모든 클래스와 Java SE에는 없지만 JCP에 의해 표준화 된 모듈 내의 클래스를 볼 수 있으며, Java 8에 비해 볼 수 있는 범위가 확장됨

3. URLClassLoader가 아닌 BuiltinClassLoader를 상속받아 ClassLoders 클래스의 내부 static 클래스로 구현됨
Application
ClassLoader
System
ClassLoader
URLClassLoader가 아닌 BuiltinClassLoader를 상속받아 ClassLoaders 클래스의 내부 static 클래스로 구현됨

 

 

 

Class Loader의 원칙

클래스 로더의 원칙은 크게 4가지로 나눌 수 있습니다.

  • 가시성 원칙
  • 유일성 원칙
  • 위임 계층
  • 언로드(Unload) 불가

가시성 원칙

이전 항목의 설명을 보면 부모-자식으로되어 있는다는 설명이 있습니다. 이처럼 각 클래스 로더들은 계층구조를 가지고 하위 클래스는 상위 클래스를 상속합니다.

 

가시성 원칙은 이러한 계층구조에서 클래스 로더를 요청받아 클래스 로더 캐시를 확인할 때

자식 클래스 로더는 부모 클래스 로더가 로드한 것을 볼 수 있지만 부모 클래스 로더는 자식 클래스 로더가 로드한 클래스를 볼 수 없다는 원칙을 말합니다.


유일성 원칙

부모가 로드한 클래스를 자식 클래스 로더가 다시 로드하지 않아야하며 이미 로드한 클래스를 다시 로드해서는 안된다는 원칙이다.
이 원칙을 통해 클래스가 정확히 한 번만 로드할 수 있다.


위임 계층

위에서 설명한 가시성 원칙과 유일성 원칙을 충족하기 위해 JVM은 클래스 로딩 요청을 아래와 같은 순서로 처리합니다.

  1. 클래스 로더 캐시
  2. 상위 클래스 로더
  3. 자기 자신
  • 이전에 로드된 클래스인지 클래스 로더 캐시를 확인하고 없다면 상위 클래스 로더에게 요청을 위임
    Application Loader는 Extension Loader에게 요청을 위임하고 Extension은 Bootstrap Loader에게 요청을 위임
  • 요청을 위임받은 Bootstrap Loader는 Java 버전에 맞게 요청한 클래스를 찾기 시작
    (Java 8버전 : rt.jar에 담긴 jdk 클래스 파일을 로딩, Java 9버전 이후 : ClassLoader 내 최상위 클래스들만 로딩)
    만약 클래스가 있다면 클래스를 반환하고 아니라면 다시 Extension Loader에게 요청을 위임
  • Extension Loader도 마찬가지로 Java 버전에 맞게 클래스를 찾고 있다면 클래스를 반환 아니라면 요청을 Application Loader에게 위임한다.
    (Java 8버전 : jre/lib/ext, java.ext.dirs, Java 9버전 이후 : Java SE 모든 클래스, JCP에 의해 표준화된 모듈 내의 클래스)
  • Application Loader도 동작은 마찬가지로 Classpath 에서 요청한 클래스를 찾은 뒤 있다면 반환
    클래스가 없다면 ClassNotFoundException 발생
    (classpath: 클래스 파일을 찾는 데 기준이 되는 파일 경로, 시스템의 모든 폴더를 JVM이 검사하도록 하는 것은 비현실적이므로 JVM에 찾아볼 파일 경로를 제공해야함)

언로드 불가

언로드 불가 원칙은 말그대로 이미 로드한 클래스를 언로드(Unload)할 수 없다는 원칙을 말합니다.

 

 

 

Class Loader의 동작 순서

클래스 로더 시스템의 크게 3가지의 순서로 실행됩니다.

  1. 로딩
  2. 링크
  3. 초기화

로딩

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

(JVM의 메서드 영역에 대해서는 JVM의 메모리 구조에서 확인바랍니다.)

  • Class, Enum, Inteface를 구분해서 저장합니다.
  • 로딩이 끝나면 해당 클래스 타입의 객체를 생성해 메모리의 Heap영역에 저장합니다.
  • 동적 로딩 : 본문의 처음에 언급했듯이 런타임 중 필요할 때마다 동적으로 메모리를 할당해서 효율적으로 메모리를 관리합니다.
  • 동작 순서는 이 글의 위임 계층에서 설명이 되어있습니다.

링크

링크 단계는 로드된 Class, Interface 등을 검증, 준비, 해석하는 과정을 말합니다.

  • Verify(검증) - Prepare(준비) - Resolve(해석) 의 과정을 거칩니다.
  • Verify
    • .class 파일이 자바 언어 명세서에 따라 코드를 제대로 잘 작성했는지, JVM 규격에 따라 검증된 컴파일러에서 파일이 생성되는지 등을 확인해 파일의 정확성을 보장
    • 만약 검증이 실패한다면 java.lang.VerifyError을 발생시켜 유효하지 않은 클래스 파일의 변경을 미연에 방지
  • Prepare
    • 메모리를 준비하는 단계
    • Class 또는 Interface에 필요한 정적 필드를 만들고 해당 필드를 기본값으로 초기화 하는 작업을 수행
      class ExampleClass {
        private static int a = 10;
      }
      
      위와 같은 코드가 있다면 int형 정적 변수인 a에 4byte의 메모리 공간을 확보하고 기본값인 0으로 초기화
    • 위의 작업 수행중 메모리가 부족하다면 java.lang.OutOfMemoryError이 발생
  • Resolve
    • 해석단계는 런타임 상수 풀(run-time constant pool)에 있는 심볼릭 참조를 직접 참조로 대체하는 과정
    • JVM의 명령어(anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface , invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield 및 putstatic)는 심볼릭 참조를 사용하는데 이러한 명령어를 해석하려면 심볼릭 참조 해석이 필요
      class ConstantPoolExample{
        public void example(){
           System.out.println("constant pool");
        }
      }
      
      이 코드를 디어셈블러 명령어인 javap -v 이름.class 를 통해 바이트코드로 분석하면 아래와 같습니다.
      #n은 상수풀의 n번 인덱스로 접근하는 바이트코드
      위의 상수풀의 심볼릭 참조를 직접참조로 대체하는 과정을 거친다.
    • #1 = Methodref          #6.#14         // java/lang/Object."<init>":()V
      #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
      #3 = String             #17            // constant pool
      #4 = Methodref          #18.#19        // java/io/PrintStream.println:(Ljava/lang/String;)V
      #5 = Class              #20            // ConstantPoolExample
      #6 = Class              #21            // java/lang/Object
      #7 = Utf8               <init>
      #8 = Utf8               ()V
      #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               example
      #12 = Utf8               SourceFile
      #13 = Utf8               constant.java
      #14 = NameAndType        #7:#8          // "<init>":()V
      #15 = Class              #22            // java/lang/System
      #16 = NameAndType        #23:#24        // out:Ljava/io/PrintStream;
      #17 = Utf8               constant pool
      #18 = Class              #25            // java/io/PrintStream
      #19 = NameAndType        #26:#27        // println:(Ljava/lang/String;)V
      #20 = Utf8               ConstantPoolExample
      #21 = Utf8               java/lang/Object
      #22 = Utf8               java/lang/System
      #23 = Utf8               out
      #24 = Utf8               Ljava/io/PrintStream;
      #25 = Utf8               java/io/PrintStream
      #26 = Utf8               println
      #27 = Utf8               (Ljava/lang/String;)V
      

초기화

초기화 단계는 로드된 각 Class나 Interface의 초기화 로직이 실행되는 과정을 말합니다.

  • 정적 변수는 코드에 명시된 원래 값이 할당
  • 초기화 블록 (static { })이 실행
  • 클래스 계층구조에서 부모에서 자식까지 한 줄씩 실행

'자바' 카테고리의 다른 글

빌드(Build)  (0) 2024.06.17
Optional  (0) 2024.04.24
[JVM] Garbage Collector  (0) 2024.04.18
[JVM] Runtime Data Area  (0) 2024.04.18
[JVM] JVM 이란  (0) 2024.04.18