역시 인터넷에서 JVM의 클래스 검색 이야기가 나와서
자바 클래스 로딩이 어떻게 이루어지고 우리가 작성한 사용자 정의 클래스들은
어떻게 검색하는지 공부해봤다
굳이 클래스 로더를 건들거나 보지않음에도 이걸 공부하는 이유는
Java 9부터 클래스 모듈 시스템인 Jigsaw가 도입되었기 때문에 자바를 공부하는 사람이라면 한번쯤 아는 게 좋지않을까 싶었다
Java 9부터 모듈 시스템인 Jigsaw가 도입되었기 때문에 각 클래스로더의 구조나 동작에 변경이 있었다.
Bootstrap ClassLoader
모든 자바 클래스는 java.lang.ClassLoader에 의해 로드된다
그 중 Object, String 같은 최상위 클래스들을 로딩한다
최상위 클래스들은 사용자 클래스들보다 먼저 있어야하기에 로드 타임 클래스 로딩이 이루어진다
Native Code로 구현되어 있으며, 운영체제 별 구현이 다르다
Java 8
jre/lib/rt.jar로 모든 JDK의 내부 클래스를 로드한다
Java 9
더 이상 /rt.jar이 존재하지 않으며 /lib 내에 모듈화되어 포함되었다
이젠 정확하게 ClassLoader 내 최상위 클래스들만 로드한다
Extension ClassLoader
최상위 클래스 외 확장클래스 로더
Java 8
URLClassLoader를 상속, jre/lib/ext 내 모든 클래스를 로드한다
Java 9
Platfrom Loader로 변경되었으며, URLClassLoader가 아닌 BuiltinClassLoader를 상속한다
Inner Static 클래스로 구현되어 있다
Application ClassLoader
사용자 환경(애플리케이션) 레벨에서 타게팅되는 모든 클래스를 로드한다
Extension ClassLoader의 하위 클래스로 -classpath에 속한 모든 클래스가 해당된다
자바 클래스로더는 세 가지 원칙을 따른다
Delegate(위임)
하위 클래스(로더)는 자신이 로딩하지 않고 상위 클래스에게 로딩을 위임한다
최상위 클래스까지 위임한 후 로딩 여부를 체크하며 내려온다
최종적으로 ClassNotFoundException까지 발생시킬 수 있다
Visibility(가시성)
하위 클래스는 상위 클래스를 바라볼 수 있어야하며, 반대로 상위 클래스는 하위 클래스 로딩을 볼 수 없다는 계층적 원칙
만약 String Class가 들어가는 사용자 클래스가 있다면 이 클래스는 최상위 클래스까지 올라간 뒤 로딩해야한다
즉 하위 클래스는 최상위 클래스로부터 로딩될 수 있어야 한다
Uninqueness(유일성)
하위 클래스는 상위 클래스로더의 유일성을 깰 수 없다
계층 원칙에 의해 하위 클래스 로딩이 상위 클래스 로더에서 이루어질 수 있기때문에 중복 로딩을 방지하는 원칙이다
자바의 클래스 로딩은 클래스 참조(로드가 아니다) 시점에 JVM에 코드가 링크되고
실제 런타임 시점에 로딩되는 Dynamic Loading을 거친다
런타임에 동적으로 클래스를 로딩한다는 건 JVM이 클래스에 대한 정보를 갖고 있지 않다는 것을 의미한다
그런데 그러면 드는 의문이 있게 된다
C의 경우엔 컴파일 시점에 모든 헤더 로딩이 이루어지는데
자바의 경우 사용자 로더의 main()부터 로딩이 이루어진다면 이미 프로그램이 실행 중인데 클래스 로더는 대체 어떻게 클래스 정보를 알게 되는걸까?
import java.lang.Object
import java.lang.String
public static void main(String[] args) run {
}
로드타임 로딩, 이 때 최상위 클래스의 로딩이 이루어진다
이 코드의 경우엔 main()을 로드할 때 Bootstrap ClassLoader에 의해 최상위 클래스
Object와 String이 로딩된다
Run Time Dynamic Loading
최상위 클래스를 제외한 클래스 로딩은 전부 이 시점에서 일어난다
그리고 아직 로드되지 않은 클래스들은 최상위 클래스의 리플렉션을 통해 클래스 정보를 알고 로드해올 수 있다
Reflaction
JDK 1.1부터 java.lang.reflact 패키지에 속한 API
근데 왜 굳이 리플렉션을 통해서 클래스를 로드해야할까?
다음 코드를 보자
public class A() {
void doSomthing() {
}
}
public class B() {
public static void main(String[] args) {
Object A = new A();
A.doSomthing(); // ERROR
}
}
사실 JVM은 클래스 A의 로드 정보를 알 수가 없다
왜냐면 동적 로딩에 의해 main()이 실행되고 객체가 생성되어 메소드가 호출될 때
그 때가 되서야 클래스가 로드되기 때문이다
리플렉션은 아직 클래스가 로드되지 않은 상태라도 이름에 의해 클래스 정보를 유추하는 기법으로
Class.getClass
Class.getName
등을 사용한다.
결론적으로 클래스(객체)의 메모리만 알고있는 상태에서 클래스의 정보를 추출해낼 수 있다
이런 자바의 클래스 로딩 방식과 리플렉션 기법에 의해 스프링 프레임워크에서는
컨테이너에 의한 동작, Annotation에 의한 객체 주입이 가능하다