내가 만든 자바 코드가 어떤 과정으로 실행되는지 알아보기 위한 "JVM 알아보기" 두 번째 포스팅입니다.

이번 글은 JVM 의 구성 요소 중 하나인 ClassLoader 에 대해서 알아보겠습니다.

 


 

JVM Architecture

 

 

JVM 의 구성요소는 크게 세 가지다.

 

  • Class Loader Subsystem
  • Runtime Data Areas
  • Execution Engine

이번 포스팅에서는 먼저 Class Loader 부터 알아보도록 하자.

 

 

ClassLoader의 동작 과정

Java 클래스는 한 번에 전부 메모리에 로드되지 않고 사용 시점에 로드된다.

이 때 클래스 로더가 JRE 에 의해 호출되고, 동적으로 클래스를 메모리에 로드한다.

 

 

클래스 로더는 주로 Loading, Linking, Initialization 세 가지 역할을 수행한다.

 

https://javatutorial.net/jvm-explained/

Loading

 

컴파일 된 ".class" 파일은 플랫폼과 기계에 독립적인 byte code 상태이다.

로딩 단계에서 클래스 로더는 ".class" 파일에서 해당 바이너리 데이터를 생성하여 메서드 영역에 저장한다.

 

각 ".class" 파일에 대해 JVM 은 메서드 영역에 다음 정보를 저장한다.

 

  • 로드된 클래스 및 직계 부모 클래스의 정규화된 이름
  • ".class" 파일이 Class, Interface, Enum 과 관련되어 있는지 여부
  • 수정자, 변수 및 메서드 정보 등

 

".class" 파일을 로드한 후에 JVM 은 Class 타입의 객체를 생성한다.

객체는 메모리의 heap 이라는 영역에 저장이 되는데, 이렇게 객체를 생성하면 메모리 내에서 ".class" 파일을 나타낼 수 있다.

 

이 객체는 java.lang 패키지에 미리 정의되어 있는 타입으로, 클래스 이름, 메서드 및 변수 정보 등과 같은 값을 가져올 수 있다.

이 객체의 참조를 얻으려면 Object 클래스의 getClass() 메서드를 사용하면 된다.

 

다음과 같이 클래스 수준의 정보를 가져올 수 있다.

 

Java

package c.jvm;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) {
        Student s1 = new Student();

        Class c1 = s1.getClass();

        System.out.println(c1.getName());

        Method m[] = c1.getDeclaredMethods();
        for (Method method : m)
            System.out.println(method.getName());

        Field f[] = c1.getDeclaredFields();
        for (Field field : f)
            System.out.println(field.getName());
    }
}

class Student {
    private String name;
    private int number;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getNumber() { return number; }
    public void setNumber(int number)
    {
        this.number = number;
    }
}

 

Output

c.jvm.Student
getName
setName
getNumber
setNumber
name
number

 

 

클래스 로더에는 일반적으로 세 가지 유형이 있다.

 

 

- Bootstrap ClassLoader

 

다른 모든 ClassLoader 의 부모로, C, C++ 과 같은 네이티브 언어로 구현된다.

Java 객체가 아니기 때문에 다음과 같이 ClassLoader 를 가져오려고 하면 null 을 리턴한다.

 

 

Java

package c.jvm;

public class Test {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(Test.class.getClassLoader());
    }
}

 

Output

null
jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7

 

 

 

JVM 을 동작시키기 위한 가장 핵심적인 Java 클래스(java.lang.* 등)를 로드한다.

java 명령어로 애플리케이션을 실행하면, JVM 위에서 애플리케이션이 동작한다.

다음과 같은 옵션을 주면 클래스를 로딩할 때 로딩하는 라이브러리들을 확인할 수 있다.

 

java -verbose:class [Class Name]

 

샘플 클래스를 실행해보면 수많은 클래스들이 로드되는 것을 확인할 수 있다.

실행환경: jdk 17.0.1

 

- Platform ClassLoader(Java9+)

 

Bootstrap ClassLoader 의 자식으로, JDK 확장 디렉토리(일반적으로 JRE의 lib/ext)에서 클래스들을 로드한다.

 

- System ClassLoader

 

Platform ClassLoader 의 자식으로, 클래스패스나 모듈패스에 있는 클래스를 로드한다.

 

 

왜 3개의 클래스 로더가 필요할까? 그 이유는 세 가지 수준의 신뢰를 나타내기 때문이다.

가장 신뢰할 수 있는 클래스는 핵심 API 클래스다. 다음은 설치된 확장과 클래스 패스의 클래스(로컬)다.

(참고) What is the use of Custom Class Loader

 

 

Linking

verification, preparation 그리고 선택적으로 resolution 을 수행한다.

 

- verification

 

파일이 올바른 형식으로 되어있고, 유효한 컴파일러에 의해 생성되었는지 여부를 검증한다.

검증에 실패하면 런타임 예외 java.lang.VerifyError 가 발생한다.

ByteCodeVerifier 에 의해 수행되며, 검증이 완료되면 클래스 파일을 컴파일할 준비가 된 것이다.

비용이 매우 크기 때문에, 옵션을 통해서 Disabled 할 수 있다.

 

 

-preparation

 

클래스 변수에 대해 메모리를 할당하고 기본값으로 초기화한다.

기본값은 다음과 같다.

 

 

- resolution(optional)

 

symbolic references를 메서드 영역에 있는 direct references 로 교체한다.

 

 

 

Initialization

이 단계에서 모든 static 변수는 정의된 값으로 할당된다.

 

 

 

 

ClassLoader 의 상속 관계

 

 

 

 

 

ClassLoader 의 특징

Java ClassLoader 에는 세 가지의 특징이 있다.

 

 

Delegation Model 위임 모델

클래스 로더는 Delegation Hierachy Algorithm 을 사용하여 Java 파일에 클래스를 로드한다.

 

Delegation Hierachy Algorithm

 

System ClassLoader-> Platform ClassLoader-> Bootstrap ClassLoader 순서로 작동한다. 

Bootstrap ClassLoader는 항상 더 높은 우선순위를 가지며, 그 다음은 Platform ClassLoader, 그 다음은 System ClassLoader 다.

 

 

  • 클래스 로딩을 요청받은 System ClassLoader 는 클래스 로딩을 직접 하지 않고 부모 클래스로더에게 위임한다.
  • 요청을 위임받은 Platform ClassLoader 도 마찬가지로 Bootstrap ClassLoader 에게 위임한다.
  • Bootstrap ClassLoader 는 앞에서 설명한 것과 같이 가장 핵심적인 Java 클래스를 로드하는 클래스 로더다. 찾고 있는 클래스가 핵심 클래스에 없다면 Platform ClassLoader, System ClassLoader 순으로 클래스를 찾는다.
  • 만약 존재하지 않는다면 ClassNotFoundException 이 발생한다.

 

 

Visibility Principle 가시성 원칙

 

 

부모 ClassLoader에 의해 로드된 클래스는 자식 ClassLoader에서 확인할 수 있지만

자식 ClassLoader에 의해 로드된 클래스는 부모 ClassLoader에서 확인할 수 없다.

 

Test.class 가 Platform ClassLoader 에 의해 로드되었다면

Platform ClassLoader 나 System ClassLoader 에서는 확인할 수 있지만, Bootstrap ClassLoader 에서는 확인할 수 없다.

 

해당 클래스가 Bootstrap ClassLoader 를 사용하여 다시 로드하려고 하면 java.lang.ClassNotFoundException 예외가 발생한다.

 

 

사용자가 java.lang.MyClass 라는 클래스를 만들었다고 가정하자.

이론적으로는 같은 패키지의 모든 필드와 메서드에 대한 패키지 액세스 권한을 얻고 java.lang 의 작동 방식을 변경할 수 있다.

하지만, java.lang 패키지의 클래스는 부트스트랩 클래스 로더에 의해 로드 되었기 때문에 가시성 원칙에 의해 JVM이 이를 차단하게 된다. 

동일한 로더가 아님 = 액세스 권한 없음

 

 

Uniqueness Property 유일성

자식 클래스 로더는 부모 클래스 로더가 로딩한 클래스를 다시 로딩하지 않는 것을 보장한다.

 

 

 

마치며

작성한 자바 코드를 클래스 파일로 만든 후 이를 ClassLoader 를 통해 로드하는 방식 및 특징을 알아보았습니다.

다음 포스팅에서는 ClassLoader 가 로드한 데이터를 저장하는 JVM 의 메모리구조에 대해 알아보도록 하겠습니다.

 

 

Reference

 

복사했습니다!