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
클래스는 런타임 중에 로딩된다.