
Java는 컴파일 시점이 아닌 런타임 시점에 클래스를 로드하고 링크 하는 동적 로딩 방식을 사용한다.
이 때 각 클래스를 동적으로 로드하는 역할을 클래스 로더가 담당한다.
동적 로딩은 로딩 - 링크 - 초기화 과정을 거쳐 명령을 실행한다.
클래스 로더 계층 구조 상 최상위 계층 클래스 로더.
자바 클래스를 로드 할 수 있는 자바 자체의 클래스 로더와 Object 클래스를 로드한다.
/jre/lib/rt.jar 와 기타 핵심 라이브러리 등 JDK 내부 클래스를 로드한다./rt.jar 가 /lib 내에 모듈화 되어 포함되어 자바 자체의 클래스 로더만 로드한다.부트스트랩 클래스 로더의 자식 클래스 로더로 확장 자바 클래스를 로드한다.
기본적으로
${JAVA_HOME}/jre/lib/ext내 클래스들을 로드하고,java.ext.dirs환경변수를 통해 로드 할 디렉토리를 설정 할 수 있다.
URLClassLoader를 상속받고 설정된(혹은 기본) 디렉토리 내 모든 클래스를 로드한다.PlatformClassLoader로 변경되었고 URLClassLoader가 아닌 BulletinClassLoader를 상속받아 Inner Static 클래스로 구현되어있다.사용자가 지정한 ${CLASSPATH}의 클래스를 로드한다.
클래스 로더 중 최하위 계층으로, 애플리케이션 레벨에서 사용자가 직접 정의하고 생성한 클래스 로더.
새로운 클래스를 로드할 경우 다음과 같은 절차를 따른다.
시스템 클래스 로더에 요청하고 시스템 클래스 로더는 확장 클래스 로더에 요청을 넘긴다.확장 클래스 로더는 부트스트랩 클래스 로더에 요청을 넘기고 부트스트랩 클래스 로더는 부트스트랩 경로(/jre/lib)에 해당 클래스가 존재하는지 확인한다.확장 클래스 로더로 요청을 넘기고 확장 클래스 로더는 확장 경로(/jre/lib/ext)에서 찾아본다.확장 클래스 로더에서도 찾을 수 없다면 시스템 클래스 로더에서 시스템 경로에서 찾는다. 이 때 찾을 수 없다면 ClassNotFoundException을 발생시킨다.클래스 로더 동작 확인
실행
- 다음과 같이 어떤 클래스 로더가 로드했는지 알 수 있다.
zulu-17을 기준으로 작성했다.System.out.println(Object.class.getClassLoader() + " loads Object.class"); System.out.println(SQLData.class.getClassLoader() + " loads SQLData.class"); System.out.println(Main.class.getClassLoader() + " loads Main.class");결과
null은부트스트랩 클래스 로더.null loads Object.class jdk.internal.loader.ClassLoaders$PlatformClassLoader@5305068a loads SQLData.class jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7 loads Main.class
public Class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world!!!");
}
}
위처럼 hello world!!!를 출력 하는 코드를 컴파일 과정 이후 실행 했을 때 클래스 로더는 Object 클래스와 HelloWorld 클래스를 읽고 로딩하기 위해 필요한 String, System 클래스를 로딩한다. 여기서 모든 클래스는 HelloWorld 클래스가 실행 되기 전 로드타임에 동적으로 로드 된다.
직전 챕터에서 배웠던 객체지향 다형성 원리를 이용해 런타임 동적 로딩을 알아보자. 우선 다음과 같이 Main, Car, SportsCar 클래스코드를 이용해 세개의 파일을 만들고 실행 해보자.
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
Car car = new SportsCar();
car.move();
}
}
---
public interface Car {
void move();
}
---
public class SportsCar implements Car {
@Override
public void move() {
System.out.println("Sports car move!!!");
}
}
정상적으로 실행되었다면 프로젝트의 실행파일 경로에 바이트 코드 결과물이 생성되고 다음과 같이 실행된다.
Hello world!
Sports car move!!!
이 때 클래스 로딩 로그를 확인해보려면 터미널을 열고 다음 명령어를 입력해보면 된다.
java classpath ${바이트코드 경로} -verbose:class ${메인클래스 명}
// ex) java classpath ./out/production/hello-world verbose:class Main
로그를 출력해보면 다음과 같이 어마어마하게 많은 클래스들이 로드타임에 로드되고 나서야 프로그램이 실행되고 실행되는 런타임에 SportsCar 클래스가 로드되는것을 확인할 수 있다.
// 명령어 입력
neo@Neos-MacBook-Air hello-world % java -classpath ./out/production/hello-world -verbose:class Main
... 중략 ...
// Car 클래스 로드.
[0.029s][info][class,load] Car source: file:/Users/neo/workspace/java/hello-world/out/production/hello-world/
... 중략 ...
Hello world!
// 이 지점에서 SportsCar 클래스가 로드 됨.
[0.029s][info][class,load] SportsCar source: file:/Users/neo/workspace/java/hello-world/out/production/hello-world/
Sports car move!!!
[0.029s][info][class,load] java.lang.Shutdown source: shared objects file
[0.029s][info][class,load] java.lang.Shutdown$Lock source: shared objects file
neo@Neos-MacBook-Air hello-world %
Q. 왜
SportsCar클래스는 런타임 중간에 로드되는걸까?A. 앞서 배웠던 객체지향 원리중 다형성 원리를 생각해보자. 상위 클래스(인터페이스)인
Car는 구체화된 클래스로 객체를 전달 받아야 한다. 그런데 프로그램이 실행되며 실행문이 해석되기 전까지 JVM은Car에SportsCar가 올지Sedan이 올지 알 수 없다. 즉, 로드타임에 어떤 클래스를 로드할지 결정 할 수 없다. 따라서 이 때SportsCar클래스는 런타임 중에 로딩된다.