미래학자
자바 동적로딩 이해(델리게이션 모델) 본문
자바 프로그램은 한 개 혹은 그 이상의 클래스들의 조합으로 실핸된다. 그리고 실행 시 모든 클래스 파일드이 한 번에 JVM 메모리에 로딩되지 않고 요청되는 순간 로딩된다. 자바의 클래스 로더가 이런 역할을 수행한다. 클래스 로더란 '.class' 바이트 코드를 읽어 들여 class 객체를 생성하는 역할을 담당한다. 즉, 클래스 로더는 클래스가 요청될 때 파일로부터 읽어 메모리로 로딩하는 역할을 하며 자바 가상 머신의 중요한 요소 중 하나다.
※ 클래스 로더가 classpath라는 환경 변수에 등록된 디렉토리에 있는 모든 클래스들을 먼저 JVM에 로딩한다. JVM에 로딩된 클래스만이 JVM에서 객체로 사용할 수 있다. 클래스 로딩은 클래스를 로딩하는 시점 또는 실행 중간에도 할 수 있다.
자바의 클래스 로딩은 세부적으로 로딩, 링크, 초기화라는 세 단계 과정을 거친다.
- 로딩 : 클래스 파일을 바이트 코드로 읽어 메모리로 가져오는 과정
- 링크 : 가장 복잡한 과정으로, 읽어본 바이트 코드가 자바 규칙을 따르는지 검증하고, 클래스에 정의된 필드, 메소드, 인터페이스들을 나타내는 데이터 구조를 준비하며, 그 클래스가 참조하는 다른 클래스를 로딩한다.
- 초기화 : 슈퍼 클래스 및 정적 필드를 초기화한다.
위 코드를 보면 Hello 클래스를 로드할 때 이 클래스가 참조하고 있는 Object, String, System 클래스가 아직 로드되지 않았으므로 Hello 클래스를 로드하는 일을 중단하고 우선 이 클래스들을 로딩한다. 이처럼 한 클래스의 로드 타임에 필요한 다른 클래스들을 동적 로딩하는 것을 로드타임 동적 로딩이라고 한다.
위는 실행할 때 매개변수로 입력받은 클래스를 런타임시 동적으로 로딩하는 예시다. Class.forName() 메소드로 리턴 되는 클래스 객체는 해당 클래스에 대한 객체가 아닌 해당 클래에 대한 메타 정보를 가지는 클래스 객체다. 실제 객체가 아닌 객체를 생성하기 위한 정보라고 생각해보자. cls.newInstance()를 하면 이제 메타 데이터를 이용하여 해당 객체를 생성한다. 이때 Object로 리턴하기 때문에 적절한 캐스팅을 해줘야 한다.
클래스 로더가 최초 클래스를 로딩할 때, '.class' 바이트 파일에서 여러 정보를 추출해서 Class 객체로 만든다. 앞서 설명했듯이 Class 클래스는 클래스의 이름, 메소드, 생성자, 필드명, 부모 클래스의 정보를 개발자가 실행하거나 참조할 수 있다. 이와 같이 클래스의 내부 정보를 보는 기법을 리플렉션이라고 하며 이에 대한 자세한 내용은 11장에서 설명하겠다. 클래스 로더는 리플렉션과 매우 밀접한 관계가 있기 때문에 클래스를 로딩하는데 사용된다.
※newInstance()를 호출하면 String 객체를 제외한 모든 클래스는 Object를 리턴한다. Stirng 클래스만 유일하게 Stirng 객체를 리턴한다.
자바에서는 클래스 로딩 매커니즘을 보다 빠르게 동작하고 쉽게 확장할 수 있도록 하기 위해 클래스 로더 간 계층구조로 되어 있다. 그리고 클래스 로딩 시 델리게이션 모델을 따르도록 하였다.
- Bootstrap Class Loader : JVM이 실행될 때 맨 처음 실행되는 클래스 로더로 $JAVA_HOME/jre/lib에 있는 JVM 실행에 필요한 가장 기본적인 라이브러리(rt.jar 등)를 로딩한다. 다른 클래스 로더와 달리 자바가 아닌 네이티브로 구현되어 있다.
- Extensions Class Loader : Bootsrap Loading 후 기본적으로 로딩되는 클래스로 $JAVA_HOME/jre/lib/ext에 있는 클래스들이 로딩된다. 이 클래들은 별도로 classpath에 잡혀 있지 않아도 로딩된다.
- System Class Loading : 다음으로 CLASS PATH에 정의되거나 JVM option에서 -cp, -classpath에 지정된 클래들이 로딩된다.
- User-Defined Class Loader : 사용자가 직접 생성해서 사용하는 클래스 로더다.
클래스 로더가 클래스 로딩을 요청받게 되면 캐시, 부모 클래스 로더, 자신 클래스 로더 순으로 클래스 로딩이 된다. 캐시에서는 클래스를 로딩한 적이 있는지 확인한다. 이전에 로딩된 클래스는 해당 클래스 로더의 캐시에 저장되고 다음 번 요청 시 캐시에 저장된 내용을 사용한다. 해당 클래스를 로딩한 적이 없다면 상위 부모 클래스 로더에게 클래스 로딩 요청을 위임한다. 클래스 로딩을 위임받은 부모 클래스 로더 또한 자신의 캐시를 먼저 확인하고 해당 클래스를 이전에 로딩한 적이 없다면 그 클래스 로더의 부모에게 클래스 로딩을 위임하는 동일한 과정을 거친다. 최상위 부트스트랩 클래스 로더까지 요청이 위임되고 이전에 클래스가 로딩된 적이 없다면 최상위 부모부터 자식 클래스 로더 순서로 클래스 로딩을 시도한다. 이러한 방식을 '델리게이션 모델'이라 한다.
다음은 위에서 설명한 로딩 메커니즘을 구현한 코드다. 그림과 함께 이해하면 클래스 로딩 메커니즘을 이해하는데 도움이 될 것이다.
클래스 로더의 loadClass() 메소드를 호출하면 우선 findLoadedClass() 메소드가 로드할 클래스가 이미 로드되어 있는지 확인한다. 로드되지 않았다면 부모 클래스에게 클래스 로딩을 위임하고 마지막으로 부트스트랩 클래스 로더까지 확인한다. 모든 클래스 로더에서 이전에 로드된 적이 없다면 부트스트랩 클래스 로더부터 지정된 위치의 물리적인 클래스 파일을 찾는다. 만약 찾을 수 없다면 ClassLoader 클래스에 선언된 findClass() 메솓를 순차적으로 실행해서 클래스 파일을 찾으려 한다. 그리고 최종적으로 resolveClass() 메소드를 실행하여 링킹 작업을 실행한다.
실제 Classloader 클래스는 추상 클래스로 객체화 될 수 없고 구현클래스를 작성해야 한다.
ClassLoader 클래스 생성자
메소드 |
설명 |
protected ClassLoader() |
시스템 클래스 로더를 부모로 가지는 클래스 로더를 생성한다. |
protected ClassLoader(ClassLoader parent) |
지정된 클래스 로더를 부모로 가지는 클래스 로더를 생성한다. |
ClassLoader 클래스의 메소드
메소드 이름 |
설명 |
defineClass |
|
findClass |
|
findLibrary |
|
findLoadeedClass |
|
findResource |
|
findResources |
|
getParent |
|
getResource |
|
getResources |
|
getSystemClassLoader |
|
loadClass |
|
resolveClass |
|
|
URL 클래스 로더
개발자가 클래스를 로딩하기 위해서는 클래스 로더 객체를 인스턴스해야 한다. 이때 URLClassLoader 클래스를 사용해보도록 하자. 이 클래스는 자바에서 기본으로 제공하는 클래스로서, 개발자가 지정한 위치, 즉 URL로부터 클래스를 로딩한다. URLClassLoader는 ClassLoader 클래스를 확장한 클래스로 ClassLoader에서 제공하는 메소드 중 findClass(), getResource() 등의 메소드가 알맞게 동작하도록 구현된 활용도 높은 클래스 로더다. JVM에서 사용하는 클래스 로더의 대부분은 URLClassLoader를 확장해서 구현한다.
URLClassLoader 클래스 생성자
메소드 | 설명 |
public URLClassLoader(URL[] urls) | 지정된 URL로부터 클래스를 로딩하는 클래스 로더를 생성한다. 해당 클래스 로더의 부모는 시스템 클래스 로더가 된다. |
public URLClassLoader(URL[] urls, ClassLoader parent) | 지정된 URL로부터 클래스를 로딩하는 클래스 로더를 생성한다. |
URLCLassLoader를 이용하면 실행 시 클래스 패스에 등록되어 있지 않은 클래스들을 URL로 지정해 로딩할 수 있다. 지정된 URL에서 로딩할 클래스 파일을 찾을 수 없다면 ClassNotFoundException이 발생한다.
위 코드는 URLClassLoader를 사용하는 예제로 파일 시스템의 test 디렉토리 아래 Hello 클래스를 로딩한다. URL 클래스는 프로토콜에 개방적이고 유연하게 만들어졌기 때문에 파일, 데이터베이스, 네트워크 등 리소스 위치나 프로토콜과 관계 없이 개방적으로 리소스에 접근할 수 있다. 다음과 같이 URL을 정의하면 HTTP, FTP를 통해 클래스를 로딩할 수 있다.
이번에 소개할 내용은 *.jar 파일로부터 클래스를 로드하는 방법이다. URLClassLoader를 이용하여 classpath가 아닌 사용자가 지정한 파일로부터 클래스를 로딩하는 예제다. classpath.properties는 jar파일이 있는 위치를 나타내는 데이터 파일이다. 이 예제는 classpath.properties 파일을 읽어 우리가 로드할 '.jar' 형태의 패키지 데이터의 경로를 얻고 이 경로로 URL을 얻는다. URL을 얻으면 URLClassLoader를 통해 jar파일에 있는 클래스를 로드할 수 있다.
CustomClassLoader 클래스에 오버로딩된 두 개의 생성자들을 주목하자. 두 생성자 모두 super 키워드를 사용해서 부모 클래스 로더를 지정하고 있다. 앞서 설명했듯이 클래스 로더는 델리게이션 모델을 사용하고 있으며, 부모 클래스 로딩 작업을 위임하기 위해 반드시 부모 클래스 로더가 지정되어야 한다.
CustomClassLoader 클래스의 부모인 java.lang.ClassLoader 클래스의 findClass() 메소드의 선언부는 protected로 선언되어 있다. 즉 자식 클래스만이 findClass() 메소드에 접근 할 수 있다. findClass() 메소드의 특징 때문에 protected 접근 제한자로 선언된 것이다. findClass() 메소드의 특징은 클래스를 매번 새롭게 로드한다는 것이다. 일반적으로 델리게ㅅ이션 모델에서는 클래스를 새롭게 로드하기 전 로딩된 캐시를 먼저 조회한 다음, 없는 경우에만 부모에게 위임하는 방식이다. 따라서 함부로 findClass() 메소드가 호출되면 델리게이션 모델을 위반하게 된다. 그렇기 때문에 CustomClassLoader 클래스에 정의된 main() 메소드에서도 loadClass() 메소드를 호출하도록 프로그래밍되었다.
loadClass() 메소드는 다음과 가은 순서로 클래스를 로딩한다.
- findLoadedClass()를 호출해서 캐시에 로드된 클래스를 찾는다.
- 부모 클래스 로더의 loadClass()를 호출해서 클래스 로딩을 시도한다.
- 마지막으로 findClass()를 사용해서 클래스 로딩을 한다.
'JAVA' 카테고리의 다른 글
리플렉션 개념 익히기 (1) | 2017.01.03 |
---|---|
자바 동작 이해 (0) | 2016.12.23 |
Effective Java - 02 Builder 패턴 (0) | 2016.02.29 |
[JAVA] Effective Java - 01 정적 팩토리 메서드 사용하기 (1) | 2016.02.26 |