JVM (3) - 클래스로더

Chan Young Jeong·2023년 2월 18일
0

All About JAVA

목록 보기
8/10
post-thumbnail

자바 클래스들은 시작할 때 한번에 로드되는 것이 아니라 애플리케이션에서 필요할 때 로드된다. 클래스로더는 Java Runtime Environment의 일부로, 컴파일된 클래스(.class)를 런타임에 동적으로 JVM에 로드하는 역할을 수행하는 모듈이다.

클래스로더 종류

클래스의 종류와 경로에 따라 사용되는 클래스로더가 다르다. 그 종류는 다음과 같다.
(자바8 기준)

BootStrap ClassLoader

  • 부트스트랩 클래스로더는 3가지 기본 클래스로더 중 최상위 클래스로더로, jre/lib/rt.jar에 담 JDK 클래스 파일을 로딩한다.

  • Native C로 구현돼 있어서, String.class.getClassLoader()는 null을 반환한다.

System.out.println(System.getProperty("sun.boot.class.path"));
 
// JVM 구현에 따라 다를 수 있지만 null은 보통 부트스트랩 클래스로더를 의미한다.
// 여기서 ArrayList 는 null을 출력하는데 그 이유는 BootStrap ClassLoader는 native code로 작성되어 있기 때문에 자바 클래스로 보이지 않는 것이다.
System.out.println(ArrayList.class.getClassLoader()); // null

Extension ClassLoader

  • 익스텐션 클래스로더는 jre/lib/ext 폴더나 java.ext.dirs 환경 변수로 지정된 폴더에 있는 클래스 파일을 로딩한다.

  • Java로 구현되어 있으며 sun.misc.Launcher 클래스 안에 static 클래스로 구현되어 있으며 , URLClassLoader를 상속하고 있다.

System.out.println(System.getProperty("java.ext.dirs")); 

//자바9
System.out.println(ClassLoader.getPlatformClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());

Application ClassLoader

  • 애플리케이션 클래스로더는 -classpath(또는 -cp)나 JAR파일 안에 있는 Manifest파일의 Class-Path 속성값으로 지정된 폴더에 있는 클래스를 로딩한다

  • Java로 구현되어 있으며 sun.misc.Launcher 클래스 안에 static 클래스로 구현되어 있으며 URLClassLoader를 상속하고 있다.

  • 개발자가 애플리케이션 구동을 위해 작성한 대부분의 클래스는 이 애플리케이션 클래스로더에 의해 로딩 된다.



System.out.println(ClassLoader.getSystemClassLoader()); // jdk.internal.loader.ClassLoaders$AppClassLoader@2e5c649
System.out.println(Thread.currentThread().getContextClassLoader());

3가지 원칙

Delegation Principle

위임 원칙은 로딩이 필요할 때 3가지 기본 클래스로더의 윗 방향으로 클래스 로딩을 위임하는 것을 말한다. 클래스를 로딩할 때 먼저 상위 클래스 로더를 확인하여 상위 클래스 로더에 있다면 해당 클래스를 사용하고 없다면 로드를 요청받은 클래스 로더가 클래스를 로드한다.

Visibility Principle

가시 범위 원칙은 하위 클래스로더는 상위 클래슬로더가 로딩한 클래스를 볼 수 있지만 , 상위 클래스로더는 하위 클래스로더가 로딩한 클래스를 볼 수 없다는 원칙이다.


public class ClassLoaderExample {
    public static void main(String args[]) {
        try {
            // 이 클래스의 클래스로더를 출력한다.
            System.out.println("ClassLoaderExample.getClass().getClassLoader(): " + ClassLoaderExample.class.getClassLoader());
 
            // 확장 클래스로더를 통해서 이 클래스를 다시 로드한다. 에러 발생한다! 가시성의 원칙에 따라 상위 클래스로더는 하위 클래스로더가 로딩한 클래스를 볼 수 없기 때문!
            Class.forName("ClassLoaderExample", true, ClassLoaderExample.class.getClassLoader().getParent());
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }
}

Uniqueness Principle

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

  • 각 클래스로더는 클래스들을 보관하는 네임스페이스를 갖는다. 클래스를 로드할 때 이미 로드된 클래스인지 확인하기 위해 네임스페이스에 보관된 FQCN(Fully Qualified Class Name)을 기준으로 클래스를 찾는다. FQCN이 같더라도 네임스페이스가 다르면, 다른 클래스로 간주된다.

의문

자바 클래스는 java.lang.ClassLoader의 인스턴스에 의해 로드 된다는 것은 알았다. 그럼 클래스로더 또한 클래스인데 누가 클래스로더를 로드할까?

This is where the bootstrap or primordial class loader comes into play.
It's mainly responsible for loading JDK internal classes, typically rt.jar and other core libraries located in the $JAVA_HOME/jre/lib directory. Additionally, the Bootstrap class loader serves as the parent of all the other ClassLoader instances.
This bootstrap class loader is part of the core JVM and is written in native code, as pointed out in the above example. Different platforms might have different implementations of this particular class loader.

즉, 부트스트랩 클래스로더가 그 역할을 한다는 것!

어떻게 클래스로더는 동작하는가?

클래스로더는 JRE의 일부분으로, JVM이 클래스를 요청하면 클래스로더는 해당 클래스를 찾고 메모리에 로딩한다. 이 때 java.lang.ClassLoader.loadClass()가 이용된다.

protected synchronized Class<?>
loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    Class c = findLoadedClass(name);
    try {
        if (c == NULL) {
            if (parent != NULL) {
                c = parent.loadClass(name, false);
            }
            else {
                c = findBootstrapClass0(name);
            }
        }
        catch (ClassNotFoundException e)
        {
            System.out.println(e);
        }
    }
}

If the class isn't already loaded, it delegates the request to the parent class loader. This process happens recursively.
Eventually, if the parent class loader doesn’t find the class, then the child class will call the java.net.URLClassLoader.findClass() method to look for classes in the file system itself.
If the last child class loader isn't able to load the class either, it throws java.lang.NoClassDefFoundError or ava.lang.ClassNotFoundException.

  1. JVM의 Method Area에 클래스가 로드되어 있는지 확인한다. 만일 로드되어 있는 경우 해당 클래스를 사용한다.
  2. Method Area에 클래스가 로드되어 있지 않을 경우, 애플리케이션 클래스로더에 클래스 로드를 요청한다.
  3. 애플리케이션 클래스로더는 확장 클래스로더에 클래스 로드를 요청한다.
  4. 확장 클래스로더는 부트스트랩 클래스로더에 클래스 로드를 요청한다.
  5. 부트스트랩 클래스로더는 부트스트랩 Classpath(JDK/JRE/LIB)에 해당 클래스가 있는지 확인한다. 클래스가 존재하지 않는 경우 확장 클래스로더가 요청을 수행하도록 한다.
  6. 확장 클래스로더는 확장 Classpath(JDK/JRE/LIB/EXT)에 해당 클래스가 있는지 확인한다. 클래스가 존재하지 않는 경우 애플리케이션 클래스로더가 요청을 수행하도록 한다.
  7. 애플리케이션 클래스로더는 애플리케이션 Classpath에 해당 클래스가 있는지 확인한다. 클래스가 존재하지 않는 경우 ClassNotFoundException을 발생시킨다

로딩 -> 링킹 -> 초기화

1. 로딩

컴파일된 클래스 파일을 메모리에 로드하는 것으로 클래스로더의 주요 작업이다.
보통 메인 클래스를 로드하는 것부터 시작됩니다. 그 외 다음과 같은 상황에서 로딩이 일어 날 수 있습니다.
1. 클래스에 선언된 정적 메서드를 호출할 때
2. 클래스나 인터페이스에 선언된 정적 필드를 접근 혹은 할당 할 때
3. 클래스의 인스턴스를 만들 때(명시적 생성,역직렬화, 리플렉션 등)
4. 리플렉션 같은 특정 자바 SE 플랫폼 클래스 라이브러리에 있는 메서드를 호출하는 경우

또한 부모 클래스를 먼저 로딩해야 자식 클래스를 로딩할 수 있다.

2.링킹

링크는 아래와 같이 3단계로 이루어집니다.

검증

  • .class 파일의 바이트코드를 자바 언어 명세서에 따라 코드를 잘 작성했는지 , JVM 규격에 따라 .class 파일이 생성되는지 등을 확인한다.
  • 내부적으로 바이트코드 검증기가 이 과정을 담당
  • 복잡한 테스트 과정으로, 오랜 시간 걸림.
  • 검증 실패시 런타임에러(java.lang.VerifyError) 발생

준비

  • 클래스가 필요로 하는 메모리를 할당하고, 클래스에 정의된 필드, 메서드, 인터페이스들을 나타내는 데이터 구조를 준비한다.
  • 이 때 정적 필드는 기본값으로 초기화된다.

분석

  • 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다. 즉 실제 메모리 주소 값으로 변경해 주는 작업을 의미한다. 분석 단계는 준비 단계를 마치고 반드시 일어나지는 않으며 구현에 따라 선택적 단계이다.

Eager 방식 : JVM이 기동할 때 Application과 Application에 관련된 Class를 모두 Loading과 Linking하는 방식
Lazy 방식 : First Actual Use 때 하나씩 Loading, Linking을 수행

3. 초기화

  • 클래스 변수들을 적절한 값으로 초기화한다. 즉, static initializer들을 수행하고, static 필드들을 설정된 값으로 초기화한다.

자바8 vs 자바9

여기서 확인


출처

https://d2.naver.com/helloworld/1230
https://www.baeldung.com/java-classloaders
https://leeyh0216.github.io/posts/java_class_loader/
https://medium.com/platform-engineer/understanding-jvm-architecture-22c0ddf09722
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html
https://blog.hexabrain.net/397
https://velog.io/@skyepodium/%ED%81%B4%EB%9E%98%EC%8A%A4%EB%8A%94-%EC%96%B8%EC%A0%9C-%EB%A1%9C%EB%94%A9%EB%90%98%EA%B3%A0-%EC%B4%88%EA%B8%B0%ED%99%94%EB%90%98%EB%8A%94%EA%B0%80

0개의 댓글