[Java] ClassLoader API

http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

클래스를 로딩하는 책임을 지니고 있는 추상 클래스. 
기본전략: 바이너리 이름(String)을 받아서 파일 이름으로 바꾸고 파일 시스템에서 해당하는 이름의 클래스 파일을 읽어들인다.
위임 모델(delegation model)을 사용하여 클래스와 리소스를 찾는다. 각각의 ClassLoader 인스턴스는 연관된 상위(parent) 클래스 로더를 가지고 있다. 자신이 찾아 보기전에 상위 클래스 로더에 요청하여 먼저 찾아본다. VM 내장 클래스 로더인 “부트스트랩 클래스 로더”는 상위 클래스 로더가 없고 자신이 다른 ClassLoader 인스턴스의 상위가 된다.
보통 JVM은 플랫폼-독립적인 방식으로 로컬 파일 시스템에서 클래스를 읽어들인다. 예를 들어 유닉스 시스템에서 VM은 CLASSPATH 환경 변수에 정의되어 있는 디렉토리에서 클래스를 로딩한다.
하지만 어떤 클래스들은 파일에서 읽어오지 않고 네트워크에서 가져오거나 애플리케이션이 동작하면서 만들어지는 것도 있다. defineClass 메서드는 바이트 배열을 Class 클래스 인스턴스로 변환한다. Class.newInstance를 사용하여 그렇게 새로 정의된 클래스 인스턴스를 만들 수 있다.
클래스로더에 의해 만들어지는 객체의 메소드와 생성자는 다른 클래스를 참조할 수도 있다. 그렇게 참조하는 클래스들을 판단하기 위해 VM은 원래 클래스를 생성한 클래스 로더의 loadClass 메서드를 호출한다.
예를 들어 네트워크 클래스 로더를 만들어 다른 서버에서 클래스 파일을 다운로드 할 수도 있다. 다음은 예제 코드다.
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass(“Main”, true).newInstance();
네트워크 클래스 로더는 반드시 findClass와 네트워크에서 클래스를 읽어올 loadClassData를 정의해야한다. 바이트코드를 다운로드 한다음 defineClass를 사용하여 class 인스턴스를 만들어야 한다. 다음은 예제 구현체다.
     class NetworkClassLoader extends ClassLoader {
         String host;
         int port;
         public Class findClass(String name) {
             byte[] b = loadClassData(name);
             return defineClass(name, b, 0, b.length);
         }
         private byte[] loadClassData(String name) {
             // load the class data from the connection
              . . .
         }
     }
 
바이너리 이름
클래스로더에 전달되는 문자열로 표현한 클래스 이름은 다음에 정의된 자바 언어 표준을 따라야 한다.
예)
   “java.lang.String”
   “javax.swing.JSpinner$DefaultEditor”
   “java.security.KeyStore$Builder$FileBuilder$1”
   “java.net.URLClassLoader$3$1”
defineClass
– protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError
바이트를 Class 클래스의 인스턴스로 변환한다. Class는 resolve를 한 다음에 사용해야 한다. 
loadClass
– public Class<?> loadClass(String name) throws ClassNotFountException
loadClass(naem, false); 호출
– public Class<?> loadClass(String name, boolean resolve) throws ClassNotFountException
기본 동작은 다음과 같다.
1. findLoadedClass(String)을 호출하여 클래스가 이미 로딩되었는지 확인한다.
2. 상위 클래스 로더의 loadClass 메서드를 호출한다. 만약 상위 클래스 로더가 null이면 VM 내장 클래스 로더를 사용한다.
3. findClass(String) 메서드를 사용하여 클래스를 찾는다.
만약에 위 과정을 통해 클래스를 찾은 뒤에 resolve 플래그가 true면 반환받은 Class 객체를 사용하여resolveClass(Class) 메서드를 호출한다.
클래스로더의 하위 클래스들은 이 메서드가 아니라 findClass(String)을 재정의 할 것을 권한다.
(이런걸 지켜야 리스코프 원칙을 지켰다고 하던가…)
findLoadedClass
– protected final Class<?> findLoadedClass(String name)
만약 이 클래스로더가 JVM에 initiating 로더로 기록되어 있다면 name에 해당하는 클래스를 반환한다. 그렇지 않으면 null을 반환한다.
findClass
– protected Class<?> findClass(String name) throws ClassNotFoundException
기본 구현체는 ClassNotFoundException을 던진다. 따라서 클래스 로더를 확장하는 클래스가 이것을 구현하여 delegation 모델을 따르는 구현체를 만드는데 사용해야 한다. loadClass 메서드에서 상위 클래스 로더를 확인 한 뒤에 호출된다.
(그냥 클래스만 찾으면 되지 꼭 delegation 모델을 따라야 하는건가… 사실 loadClass가 public이라 그것도 재정의하면 그만인것을. 강요하려면 하고 말려면 말지 어중간한거 아닌가..)
resolveClass
– protected final void resolveClass(Class<?> c)
해당 클래스를 링크한다. 이 메서드는 클래스 로더가 클래스를 링크할 때 사용된다. 만약 클래스 c가 이미 링크되어 있다면 이 메서드는 그냥 끝난다. 그렇지 않은 경우라면 자바 언어 스팩의 “Execution” 챕터에 기술되어 있는대로 클래스를 링크한다.
링크
링크하기(Linking)란 클래스 또는 인터페이스 타입의 바이너리를 가져와서 JVM의 런타임에 연결하여 실행 할 수 있는 상태로 만드는 것이다. 
링크 과정은 세 가지 세부 활동으로 구성된다: verification, preparation, resolution
링크 활동 구현내용은 달라질 수 있다. 예를 들어 클래스가 사용되는 순간에 개별적으로 클래스나 인터페이스에 있는 심볼릭 레퍼런스를 확정하거나(lazy or late resolution), 검증하고나서 바로 확정할 수도 있다.(static). 즉 어떤 구현체에서는 클래스나 인터페이스를 초기화 한 이후에도 확정(resolution) 프로세스가 계속 될 수 있다는 것이다.
Verification, Preparation, Resolution
Verification: 클래스나 인터페이스의 바이너리가 구조적으로 올바른지 확인한다. 검증에 실패하면 LinkageError의 하위 클래스 중 하나인 VerifyError가 발생한다.
Preparation: 클래스나 인터페이스의 static 필드를 만들고 그런 필드들을 기본 값으로 초기화 하는 과정이 포함된다. 이 과정 중에 JVM 코드를 실행할 필요가 없다. 명시적인 static 필드 initializer는 이 과정이 아니라 initialization 과정에서 실행된다.
Resolution: 심볼릭 레퍼런스는 resolution단계를 지나야지 사용될 수 있다. 심볼릭 레퍼런스가 유효하고 반복적으로 사용되면 다이렉트 레퍼런스로 교체되어 보다 효율적으로 처리된다.
만약 이 단계를 지나다가 IncompatibleClassChangeError를 포함한 이 하위 에러들이 발생할 수 있다. IllegalAccessErrorInstantiationError, NoSuchFieldError, NoSuchMethodError
추가적으로 구현체를 찾을 수 없는 native 메서드를 선언한 클래스에서는 UnsatisfiedLinkError가 발생할 수 있다. 
하악 하악.. 오늘은 여기까지.

자바의 레퍼런스 클래스 사용 가이드라인

참조: http://www.ibm.com/developerworks/java/library/j-refs/

SoftReference, WeakReference, PhantomReference 클래스들이 Java2에 추가되기 전에는 오직 Strong Reference만 사용할 수 있었다.

Object obj = new Object();

여기서 obj는 힙에 저장되어 있는 개체를 참조한다. obj 레퍼런스가 존재하는한 가비지 컬렉터Garbage Collector는 저장소를 비우지 않을 것이다.

obj가 스코프를 벗어나거나 null이 될 때야 비로소 해당 객체를 참조하는 레퍼런스가 없다는 가정하에 gc의 대상이 된다. 하지만, gc의 대상이 된다고 해서 무조건 자원을 반납하는게 아니라는 것이다. 왜냐면, gc 알고리즘이 매우 다양하고 그 중에 몇몇 알고리즘은 나이 많고, 오래 산 객체들을 짧게 산 객체들 보다 덜 검사하는 경향이 있다.  따라서 gc에 의해 가용한 객체가 절대로 반환되지 않을 수도 있다. 만약에 gc가 객체 자원을 반납하는 것보다 먼저 프로그램이 종료를 하면 문제가 발생할 수 있다. 따라서, 기본적으로 gc의 대상이 된다고 해서 자원을 반드시 반납한다는 것을 절대로 보장할 수 없다.

이 정보는 레퍼런스 클래스를 분석할 때 중요하다. 비록 특정 문제에서 유용한 클래스들이기는 하지만, gc의 기본 특징 때문에 여러분이 생각하는 것 만큼 유용하지는 않을 것이다. Soft, Weak 그리고 Phantom 레퍼런스 객체들은 gc를 막지 않고 힙 객체를 참조하는 세 가지 방법을 제공한다.

  • Strongly reachable: strong reference로 참조할 수 있는 객체
  • Softly reachable: Strongly reachable 하진 않지만, soft reference로 참조할 수 있는 객체
  • Weakly reachable: 생략.
  • Phantomly reachable: 생략.
  • Clear: 객체의 레퍼런스 필드를 null로 설정한다. and declaring the object in the heap that the reference class referred to as finalizable.

SoftReference 클래스

메모리를 지각하는Memory-sensitive 캐시에 대한 레퍼런스로 사용하는 클래스이다. JVM이 out-of-memory를 발생하기 직전에 비워버리는 레퍼런스로 객체를 참조한다. 중요한 건 gc가 동작할 때, Softly reachable한 객체는 자원을 해제 할 수도 있고 그렇지 않을 수도 있다는 것이다. 객체 자원 해제는 gc 알고리즘과 gc 가 작업을 하는 시점에 가용한 메모리 양과 관계가 있다.

WeakReference 클래스

WeakReference 클래스는 기본적인 맵핑canonicalized mappings에 대한 레퍼런스로 사용하는 클래스이다. 오래 살 필요가 없고 다시 생성하는 비용이 비싸지 않는 객체에 대한 레퍼런스로 유용하다. 중요한 것은 gc 가 발동하면, Weakly Reachable한 객체의 자원을 반납한다는 것이다. 하지만, weakly reachable 한 객체를 찾고 자원을 반납하기 까지는 gc가 몇 번 발동해야 할 것이다.

PhantomReference 클래스

PhantomReference 클래스는 수집 되기 직전에 어떤 일을 수행하고 싶을 때 사용한다. ReferenceQueue와 함께 사용해야 한다.

…나머지 생략…
– 위의 세 가지 레퍼런스가 소멸 되는 과정
– 위의 레퍼런스 클래스의 get()과 ReferenceQueue의 poll()을 사용한 gc 테스트
– Weak 레퍼런스 만들려면 기본 Strong References는 null로 설정해야 함
– Soft, Weak, Phantom 레퍼런스를 다시 생성하는 방법.(gc가 발동할 수 있다는 가정 하게 코딩 해야 함.)