자바 클래스들은 시작할 때 한번에 로드되는 것이 아니라 애플리케이션에서 필요할 때 로드된다. 클래스로더는 Java Runtime Environment의 일부로, 컴파일된 클래스(.class)를 런타임에 동적으로 JVM에 로드하는 역할을 수행하는 모듈이다.
클래스의 종류와 경로에 따라 사용되는 클래스로더가 다르다. 그 종류는 다음과 같다.
(자바8 기준)
부트스트랩 클래스로더는 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
익스텐션 클래스로더는 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());
애플리케이션 클래스로더는 -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가지 기본 클래스로더의 윗 방향으로 클래스 로딩을 위임하는 것을 말한다. 클래스를 로딩할 때 먼저 상위 클래스 로더를 확인하여 상위 클래스 로더에 있다면 해당 클래스를 사용하고 없다면 로드를 요청받은 클래스 로더가 클래스를 로드한다.
가시 범위 원칙은 하위 클래스로더는 상위 클래슬로더가 로딩한 클래스를 볼 수 있지만 , 상위 클래스로더는 하위 클래스로더가 로딩한 클래스를 볼 수 없다는 원칙이다.
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();
}
}
}
부모가 로드한 클래스는 자식 클래스로더가 다시 로드하지 않아야 하며 이미 로딩한 클래스는 로드해서는 안 된다는 원칙이다. 이 원칙을 통해 클래스가 정확히 한 번만 로드될 수 있습니다.
자바 클래스는 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. 클래스에 선언된 정적 메서드를 호출할 때
2. 클래스나 인터페이스에 선언된 정적 필드를 접근 혹은 할당 할 때
3. 클래스의 인스턴스를 만들 때(명시적 생성,역직렬화, 리플렉션 등)
4. 리플렉션 같은 특정 자바 SE 플랫폼 클래스 라이브러리에 있는 메서드를 호출하는 경우
또한 부모 클래스를 먼저 로딩해야 자식 클래스를 로딩할 수 있다.
링크는 아래와 같이 3단계로 이루어집니다.
Eager 방식 : JVM이 기동할 때 Application과 Application에 관련된 Class를 모두 Loading과 Linking하는 방식
Lazy 방식 : First Actual Use 때 하나씩 Loading, Linking을 수행
출처
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