'2016/08'에 해당되는 글 15건

  1. 2016.08.04 JVM ClassLoader의 작동원리
  2. 2016.08.04 클래스로더 1, 동적인 클래스 로딩과 클래스로더 3
  3. 2016.08.04 java class loader #2
  4. 2016.08.04 java class loader #1 1
  5. 2016.08.04 [Java] ClassLoader API

JVM ClassLoader의 작동원리

Language/JAVA 2016. 8. 4. 15:48

개발을 하거나 WAS를 기동하면 ClassNotFoundException 을 많이 만나게 됩니다.


왜 클래스를 찾을 수 없는지 원리를 알아보기 위해 정리를 해보았습니다. 


ClassLoader의 작동원리를 살펴보기 전에 먼저 알아야 할 용어를 정리하면.


Class - Java 프로그램을 컴파일하여 생성된 .class 

Class 객체 - 일반객체를 만들기 위해 JVM 메모리에 로드된 .class에 대한 객체 

일반 객체 - Class 객체로 생성한 객체 


ClassLoader는 작성한 Java Byte Code를 JVM메모리상에 올려주는 역할을 하는 것이고, 프로그래머는 Class 객체를 이용하여 일반객체를 생성할 수 있습니다. 즉 메모리 내에서 Class 객체를 매개로 하여 일반객체를 생성합니다.


ClassLoader는 기본적으로 Unload 기능을 제공하지 않습니다.. GC에 의하여 자동 Unload되며, Static으로 선언된 경우에는 GC에서 Unload하지 않기 때문에 프로그래머는 항상 Static으로 선언된 클래스를 사용할 수 있습니다.





Java ClassLoader의 구조(Delegation Model, 위임구조)


Bootstrap ClassLoader - 모든 ClassLoader의 부모 ClassLoader 

Extensions ClassLoader - Bootstrap ClassLoader를 상속하며, lib/ext 디렉토리에 있는 클래스를 로드 

System-Classpath ClassLoader - Extensions ClassLoader를 상속하며, CLASSPATH에 잡혀 있는 모든 클래스, java.class.path 시스템 프라퍼티, -cp 혹은 -classpath에 잡혀 있는 모든 클래스를 로드 


ps.구조도는 첨부하였습니다.



클래스 로더의 delegation model에서 클래스를 찾을 경우 

Cache -> Parent -> Self 순서로 찾습니다. 


클래스 생성을 요청받으면 먼저 Cache를 검색하여 이전에 요청이 있었는지 파악하며, 없을경우 상위 클래스 로더에서 찾습니다. 

최상위 클래스로더인 Bootstrap ClassLoader 에도 없을 경우 작업한 소스의 경로에서 클래스를 찾아서 생성합니다.



출처 - http://open.egovframe.org/nforges/information/share/4082/.do


'Language > JAVA' 카테고리의 다른 글

GC  (0) 2016.08.05
Do You Really Get Classloaders?  (0) 2016.08.04
클래스로더 1, 동적인 클래스 로딩과 클래스로더  (3) 2016.08.04
java class loader #2  (0) 2016.08.04
java class loader #1  (1) 2016.08.04
:

클래스로더 1, 동적인 클래스 로딩과 클래스로더

Language/JAVA 2016. 8. 4. 15:35

동적인 클래스 로딩

자바는 동적으로 클래스를 읽어온다. 즉, 런타임에 모든 코드가 JVM에 링크된다. 모든 클래스는 그 클래스가 참조되는 순간에 동적으로 JVM에 링크되며, 메모리에 로딩된다. 자바의 런타임 라이브러리([JDK 설치 디렉토리]/jre/lib/rt.jar) 역시 예외가 아니다. 이러한 동적인 클래스 로딩은 자바의 클래스로더 시스템을 통해서 이루어지며, 자바가 기본적으로 제공하는 클래스로더는 java.lang.ClassLoader를 통해서 표현된다. JVM이 시작되면, 부트스트랩(bootstrap) 클래스로더를 생성하고, 그 다음에 가장 첫번째 클래스인 Object를 시스템에 읽어온다.

런타임에 동적으로 클래스를 로딩하다는 것은 JVM이 클래스에 대한 정보를 갖고 있지 않다는 것을 의미한다. 즉, JVM은 클래스의 메소드, 필드, 상속관계 등에 대한 정보를 알지 못한다. 따라서, 클래스로더는 클래스를 로딩할 때 필요한 정보를 구하고, 그 클래스가 올바른지를 검사할 수 있어야 한다. 만약 이것을 할 수 없다면, JVM은 .class 파일의 버전이 일치하지 않을 수 있으며, 또한 타입 검사를 하는 것이 불가능할 것이다. JVM은 내부적으로 클래스를 분석할 수 있는 기능을 갖고 있으며, JDK 1.1부터는 개발자들이 리플렉션(Reflection)을 통해서 이러한 클래스의 분석을 할 수 있도록 하고 있다.

로드타임 동적 로딩(load-time dynamic loading)과 런타임 동적 로딩(run-time dynamic loading)

클래스를 로딩하는 방식에는 로드타임 동적 로딩(load-time dynamic loading)과 런타임 동적 로딩(run-time dynamic loading)이 있다. 먼저 로드타임 동적 로딩에 대해서 알아보기 위해 다음과 코드를 살펴보자.

  public class HelloWorld {
     public static void main(String[] args) {
        System.out.println("안녕하세요!");
     }
  }


HelloWorld 클래스를 실행하였다고 가정해보자. 아마도, 명령행에서 다음과 같이 입력할 것이다.

  $ java HelloWorld


이 경우, JVM이 시작되고, 앞에서 말했듯이 부트스트랩 클래스로더가 생성된 후에, 모든 클래스가 상속받고 있는 Object 클래스를 읽어온다. 그 이후에, 클래스로더는 명령행에서 지정한 HelloWorld 클래스를 로딩하기 위해, HelloWorld.class 파일을 읽는다. HelloWorld 클래스를 로딩하는 과정에서 필요한 클래스가 존재한다. 바로 java.lang.String과 java.lang.System이다. 이 두 클래스는 HelloWorld 클래스를 읽어오는 과정에서, 즉 로드타임에 로딩된다. 이 처럼, 하나의 클래스를 로딩하는 과정에서 동적으로 클래스를 로딩하는 것을 로드타임 동적 로딩이라고 한다.

이제, 런타임 동적 로딩에 대해서 알아보자. 우선, 다음의 코드를 보자.

  public class HelloWorld1 implements Runnable {
     public void run() {
        System.out.println("안녕하세요, 1");
     }
  }
  public class HelloWorld2 implements Runnable {
     public void run() {
        System.out.println("안녕하세요, 2");
     }
  }


이 두 클래스를 Runnable 인터페이스를 구현한 간단한 클래스이다. 이제 실제로 런타임 동적 로딩이 일어나는 클래스를 만들어보자.

  public class RuntimeLoading {
     public static void main(String[] args) {
        try {
           if (args.length < 1) {
              System.out.println("사용법: java RuntimeLoading [클래스 이름]");
              System.exit(1);
           }
           Class klass = Class.forName(args[0]);
           Object obj = klass.newInstance();
           Runnable r = (Runnable) obj;
           r.run();
        } catch(Exception ex) {
           ex.printStackTrace();
        }
     }
  }


위 코드에서, Class.forName(className)은 파리미터로 받은 className에 해당하는 클래스를 로딩한 후에, 그 클래스에 해당하는 Class 인스턴스(로딩한 클래스의 인스턴스가 아니다!)를 리턴한다. Class 클래스의 newInstance() 메소드는 Class가 나타내는 클래스의 인스턴스를 생성한다. 예를 들어, 다음과 같이 한다면 java.lang.String 클래스의 객체가 생성된다.

  Class klass = Class.forName("java.lang.String");
  Object obj = klass.newInstance();


따라서, Class.forName() 메소드가 실행되기 전까지는 RuntimeLoading 클래스에서 어떤 클래스를 참조하는 지 알수 없다. 다시 말해서, RuntimeLoading 클래스를 로딩할 때는 어떤 클래스도 읽어오지 않고, RuntimeLoading 클래스의 main() 메소드가 실행되고 Class.forName(args[0])를 호출하는 순간에 비로서 args[0]에 해당하는 클래스를 읽어온다. 이처럼 클래스를 로딩할 때가 아닌 코드를 실행하는 순간에 클래스를 로딩하는 것을 런타임 동적 로딩이라고 한다.

다음은 RuntimeLoading 클래스를 명령행에서 실행한 결과를 보여주고 있다.

  $ java RuntimeLoading HelloWorld1
  안녕하세요, 1


Class.newInstance() 메소드와 관련해서 한 가지 알아둘 점은 해당하는 클래스의 기본생성자(즉, 파라미터가 없는)를 호출한다는 점이다. 자바는 실제로 기본생성자가 코드에 포함되어 있지 않더라도 코드를 컴파일할 때 자동적으로 기본생성자를 생성해준다. 이러한 기본생성자는 단순히 다음과 같이 구성되어 있을 것이다.

  public ClassName() {
     super();
  }


ClassLoader

자바는 클래스로더를 사용하고, 클래스를 어떻게 언제 JVM으로 로딩하고, 언로딩하는지에 대한 특정한 규칙을 갖고 있다. 이러한 규칙을 이해해야, 클래스로더를 좀 더 유용하게 사용할 수 있으며 개발자가 직접 자신만의 커스텀 클래스로더를 작성할 수 있게 된다.

클래스로더의 사용

이 글을 읽는 사람들은 거의 대부분은 클래스로더를 프로그래밍에서 직접적으로 사용해본 경험이 없을 것이다. 클래스로더를 사용하는 것은 어렵지 않으며, 보통의 자바 클래스를 사용하는 것과 완전히 동일하다. 다시 말해서, 클래스로더에 해당하는 클래스의 객체를 생성하고, 그 객체의 특정 메소드를 호출하기만 하면 된다. 간단하지 않은가? 다음의 코드를 보자.

  ClassLoader cl = . . . // ClassLoader의 객체를 생성한다.
  Class klass = null;
  try {
     klass = cl.loadClass("java.util.Date");
  } catch(ClassNotFoundException ex) {
     // 클래스를 발견할 수 없을 경우에 발생한다.
     ex.printStackTrace();
  }


일단 클래스로더를 통해서 필요한 클래스를 로딩하면, 앞의 예제와 마찬가지로 Class 클래스의 newInstance() 메소드를 사용하여 해당하는 클래스의 인스턴스를 생성할 수 있게 된다. 형태는 다음과 같다.

  try {
     Object obj = klass.newInstance();
  } catch(InstantiationException ex) {
     ....
  } catch(IllegalAccessException ex) {
     ....
  } catch(SecurityException ex) {
     ....
  } catch(ExceptionIninitializerError error) {
     ...
  }


위 코드를 보면, Class.newInstance()를 호출할 때 몇개의 예외와 에러가 발생하는 것을 알 수 있다. 이것들에 대한 내용은 Java API를 참고하기 바란다.

자바 2의 클래스로더

자바 2 플랫폼에서 클래스로더의 인터페이스와 세만틱(semantic)은 개발자들이 자바 클래스로딩 메커니즘을 빠르고 쉽게 확장할 수 있도록 하기 위해 몇몇 부분을 재정의되었다. 그 결과로, 1.1이나 1.0에 맞게 작성된 (커스텀 클래스로더를 포함한) 클래스로더는 자바 2 플랫폼에서는 제기능을 하지 못할 수도 있으며, 클래스로더 사용하기 위해 작성했던 코드를 재작성하는 것이 그렇게 간단하지만은 않다.

자바 1.x와 자바 2에서 클래스로더에 있어서 가장 큰 차이점은 자바 2의 클래스로더는 부모 클래스로더(상위 클래스가 아니다!)를 갖고 있다는 점이다. 자바 1.x의 클래스로더와는 달리, 자바 2의 클래스로더는 부모 클래스로더가 먼저 클래스를 로딩하도록 한다. 이를 클래스로더 딜리게이션 모델(ClassLoader Delegation Model)이라고 하며, 이것이 바로 이전 버전의 클래스로더와 가장 큰 차이점이다.

자바 2의 클래스로더 딜리게이션 모델에 대해 구체적으로 알아보기 위해 로컬파일시스템과 네트워크로부터 클래스를 읽어와야 할 필요가 있다고 가정해보자. 이 경우, 쉽게 로컬파일시스템의 jar 파일로부터 클래스를 읽어오는 클래스로더와 네트워크로부터 클래스를 읽어오는 클래스로더가 필요하다는 것을 생각할 수 있다. 이 두 클래스로더를 각각 JarFileClassLoader와 NetworkClassLoader라고 하자.

JDK 1.1에서, 커스텀 클래스로더를 만들기 위해서는 ClassLoader 클래스를 상속받은 후에 loadClass() 메소드를 오버라이딩하고, loadClass() 메소드에서 바이트코드를 읽어온 후, defineClass() 메소드를 호출하면 된다. 여기서 defineClass() 메소드는 읽어온 바이트코드로부터 실제 Class 인스턴스를 생성해서 리턴한다. 예를 들어, JarFileClassLoader는 다음과 같은 형태를 지닐 것이다.

  public class JarFileClassLoader extends ClassLoader {
     ...
     private byte[] loadClassFromJarFile(String className) {
        // 지정한 jar 파일로부터 className에 해당하는 클래스의
        // 바이트코드를 byte[] 배열로 읽어온다.
        ....
        return byteArr;
     }
     
     public synchronized class loadClass(String className, boolean resolveIt)
        throws ClassNotFoundException {
        
        Class klass = null;
        
        // 클래스를 로드할 때, 캐시를 사용할 수 있다.
        klass = (Class) cache.get(className);
        
        if (klass != null) return klass;
        
        // 캐시에 없을 경우, 시스템 클래스로더로부터
        // 지정한 클래스가 있는 지 알아본다.
        try {
           klass = super.findSystemClass(className);
           return klass;
        } catch(ClassNotFoundException ex) {
           // do nothing
        }
        
        // Jar 파일로부터 className이 나타내는 클래스를 읽어온다.
        byte[] byteArray = loadClassFromJarFile(className);
        klass = defineClass(byteArray, 0, byteArray.length);
        if (resolve)
           resolveClass(klass);
        cache.put(className, klass); // 캐시에 추가
        return klass;
     }
  }


위의 개략적인 코드를 보면, 시스템 클래스로더에게 이름이 className인 클래스가 존재하는 지 요청한다. (여기서 시스템 클래스로더 또는 primordial 시스템 클래스로더는 부트스트랩 클래스로더이다). 그런 후에, 시스템 클래스로더로부터 클래스를 읽어올 수 없는 경우 Jar 파일로부터 읽어온다. 이 때, className은 완전한 클래스 이름(qualified class name; 즉, 패키지이름을 포함한)이다. NetworkClassLoader 클래스 역시 이 클래스와 비슷한 형태로 이루어져 있을 것이다. 이 때, 시스템 클래스로더와 그 외의 다른 클래스로더와의 관계는 다음 그림과 같다.



위 그림을 보면, 각각의 클래스로더는 오직 시스템 클래스로더와 관계를 맺고 있다. 다시 말해서, JarFileClassLoader는 NetworkClassLoader나 AppletClassLoader와는 관계를 맺고 있지 않다. 이제, A라는 클래스가 내부적으로 B라는 클래스를 사용한다고 가정해보자. 이 때, 만약 A 클래스는 네트워크를 통해서 읽어오고, B라는 클래스는 Jar 파일을 통해서 읽어와야 한다면? 이 경우에 어떻게 해야 하는가? 쉽사리 해결책이 떠오르지 않을 것이다. 이러한 문제는 JarFileClassLoader와 NetworkClassLoader 간에 유기적인 결합을 할 수 없기 때문에 발생한다.

자바 2에서는 이러한 문제를 클래스로더 딜리게이션 모델을 통해서 해결하고 있다. 즉, 특정 클래스로더 클래스를 읽어온 클래스로더(이를 부모 클래스로더라고 한다)에게 클래스 로딩을 요청하는 것이다. 다음의 그림을 보자.


이 그림은 자바 2에서 클래스로더간의 관계를 보여주고 있다. 이 경우, NetworkClassLoader 클래스는 JarFileClassLoader가 로딩하고, JarFileClassLoader 클래스는 AppClassLoader가 로딩하였음을 보여준다. 즉, JarFileClassLoader는 NetworkClassLoader의 부모 클래스로더가 되고, AppClassLoader는 JarFileClassLoader의 부모 클래스로더가 되는 것이다.

이 경우, 앞에서 발생했던 문제가 모두 해결된다. A 클래스가 필요하면, 가장 먼저 NetworkClassLoader에 클래스로딩을 요청한다. 그럼, NetworkClassLoader는 네트워크로부터 A 클래스를 로딩할 수 있으므로, A 클래스를 로딩한다. 그런 후, A 클래스는 B 클래스를 필요로 한다. B 클래스를 로딩하기 위해 NetworkClassLoader는 JarFileClassLoader에 클래스 로딩을 위임(delegation)한다. JarFileClassLoader는 Jar 파일로부터 B 클래스를 읽어온 후 NetworkClassLoader에게 리턴할 것이며, 따라서 NetworkClassLoader는 Jar 파일에 있는 B 클래스를 사용할 수 있게 된다. 앞의 JDK 1.1에서의 클래스로더 사이의 관계에 비해 훨씬 발전적인 구조라는 것을 알 수 있다.

앞에서 말했듯이, 자바 2에서는 몇몇 클래스로더 메커니즘을 재정의하였다. 이 때문에, JDK 1.1에서의 클래스로더에 관한 몇몇개의 규칙이 깨졌다. 먼저, loadClass() 메소드를 더 이상 오버라이딩(overriding) 하지 않고, 대신 findClass()를 오버라이딩한다. loadClass() 메소드는 public에서 protected로 변경되었으며, 실제 JDK1.3의 ClassLoader 클래스의 소크 코드를 보면 다음과 같이 정의되어 있다.

  // src/java/lang/ClassLoader.java
  public abstract class ClassLoader {
      /*
       * The parent class loader for delegation.
       */
      private ClassLoader parent;
      
      protected synchronized Class loadClass(String name, boolean resolve)
      throws ClassNotFoundException
      {
          // First, check if the class has already been loaded
          Class c = findLoadedClass(name);
          if (c == null) {
              try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                      c = findBootstrapClass0(name);
                  }
              } catch (ClassNotFoundException e) {
                  // If still not found, then call findClass in order
                  // to find the class.
                  c = findClass(name);
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
      ....
  }


위 코드를 보면 부모 클래스로더로부터 먼저 클래스 로딩을 요청하고, 그것이 실패할 경우(즉, catch 블럭)에 비로소 직접 클래스를 로딩한다. 여기서 그렇다면 부모 클래스는 어떻게 결정되는 지 살펴보자. 먼저 JDK 1.3의 ClassLoader 클래스는 다음과 같은 두 개의 생성자를 갖고 있다.

  protected ClassLoader(ClassLoader parent) {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = parent;
      initialized = true;
  }
  protected ClassLoader() {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = getSystemClassLoader();
      initialized = true;
  }


이 두 코드를 살펴보면, 부모 클래스로더를 지정하지 않을 경우, 시스템 클래스로더를 부모 클래스로더로 지정하는 것을 알 수 있다. 따라서 커스텀 클래스로더에서 부모 클래스로더를 지정하기 위해서는 다음과 같이 하면 된다.

  public class JarFileClassLoader extends ClassLoader {
     public JarFileClassLoader () {
        super(JarFileClassLoader.class.getClassLoader());
        // 다른 초기화 관련 사항
     }
     ....
     public Class findClass(String name) {
        // 지정한 클래스를 찾는다.
     }
  }


모든 클래스는 그 클래스에 해당하는 Class 인스턴스를 갖고 있다. 그 Class 인스턴스의 getClassLoader() 메소드를 통해서 그 클래스를 로딩한 클래스로더를 구할 수 있다. 즉, 위 코드는 JarFileClassLoader 클래스를 로딩한 클래스로더를 JarFileClassLoader 클래스로더의 부모 클래스로더로 지정하는 것이다. (실제로 커스텀 클래스로더를 구현하는 것에 대한 내용은 이 Article의 시리중에서 3번째에 알아보기로 한다).

JVM에서 부모 클래스로더를 갖지 않은 유일한 클래스로더는 부트스트랩 클래스로더이다. 부트스트랩 클래스로더는 자바 런타임 라이브러리에 있는 클래스를 로딩하는 역할을 맡고 있으며, 항상 클래스로더 체인의 가장 첫번째에 해당한다. 기본적으로 자바 런타임 라이브러리에 있는 모든 클래스는 JRE/lib 디렉토리에 있는 rt.jar 파일에 포함되어 있다.

결론

이번 Article에서는 자바에서 클래스 로딩이 동적으로 이루어지면, 클래스 로딩 방식에서는 로드타임 로딩과 런타임 로딩의 두 가지 방식이 있다는 것을 배웠다. 그리고 자바 2에서의 클래스로딩이 클래스로더 딜리게이션 모델(Classloader Delegation Model)을 통해서 이루어진다는 점과 이 모델에 자바 1.x에서의 클래스로딩 메커니즘과 어떻게 다르며, 어떤 장점이 있는 지 알아보았다. 다음 Article에서는 자바 2에서 기본적으로 제공하는 클래스로더에 대해서 알아보기로 한다.

'Language > JAVA' 카테고리의 다른 글

Do You Really Get Classloaders?  (0) 2016.08.04
JVM ClassLoader의 작동원리  (0) 2016.08.04
java class loader #2  (0) 2016.08.04
java class loader #1  (1) 2016.08.04
[Java] ClassLoader API  (0) 2016.08.04
:

java class loader #2

Language/JAVA 2016. 8. 4. 15:07

ClassLoader 2

 

  // src/java/lang/ClassLoader.java
  public abstract class ClassLoader {
      /*
       * The parent class loader for delegation.
       */
      private ClassLoader parent;
      
      protected synchronized Class loadClass(String name, boolean resolve)
      throws ClassNotFoundException
      {
          // First, check if the class has already been loaded
          Class c = findLoadedClass(name);
          if (c == null) {
              try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                      c = findBootstrapClass0(name);
                  }
              } catch (ClassNotFoundException e) {
                  // If still not found, then call findClass in order
                  // to find the class.
                  c = findClass(name);
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
      ....
  }

코드 4.


코드 4 JDK1.3 ClassLoader 클래스로서 부모 클래스로더로부터 먼저 클래스 로딩을 요청하고그것이 실패할 경우에 직접 클래스를 로딩합니다여기서 부모 클래스가 어떻게 결정되는 지를 보면 JDK 1.3 ClassLoader 클래스는 다음과 같은  개의 생성자를 갖고 있습니다.

  protected ClassLoader(ClassLoader parent) {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = parent;
      initialized = true;
  }
  protected ClassLoader() {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = getSystemClassLoader();
      initialized = true;
  }

코드 5.

코드 5 보면부모 클래스로더를 지정하지 않을 경우에는 시스템 클래스로더를 부모 클래스로더로 지정하고 있습니다커스텀 클래스로더에서 부모 클래스로더를 지정하기 위해서는 코드 6 같이 하면 됩니다.

  public class JarFileClassLoader extends ClassLoader {
     public JarFileClassLoader () {
        super(JarFileClassLoader.class.getClassLoader());
        // 
다른 초기화 관련 사항
     }
     ....
     public Class findClass(String name) {
        // 
지정한 클래스를 찾는다.
     }
  }

코드 6.


모든 클래스는  클래스에 해당하는 Class 인스턴스를 가지고 있습니다 Class 인스턴스의 getClassLoader() 메소드를 통해서  클래스를 로딩한 클래스로더를 구할  있습니다코드 6에서는 JarFileClassLoader 클래스를 로딩한 클래스로더를 JarFileClassLoader 클래스로더의 부모 클래스로더로 지정하는 것입니다. 
JVM
에서 부모 클래스로더를 갖지 않은 유일한 클래스로더는 부트스트랩 클래스로더로서 부트스트랩 클래스로더는 자바 런타임 라이브러리에 있는 클래스를 로딩하는 역할을 맡고 있으며항상 클래스로더 체인의   번째에 해당합니다. Java에서는 같은 클래스일지라도다른 클래스로더에 의해서 로딩되었다면 다른 클래스로 구분이 됩니다.

 

Example


  public class ToolsJarLoader extends URLClassLoader {

private static ToolsJarLoader mInstance;

public static ToolsJarLoader getInstance() {

if(mInstance == null) {

mInstance = new                ToolsJarLoader(((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs());

}

return mInstance;

}


    public ToolsJarLoader(URL[] urls) {

        super(urls);

    }

    

    @Override

    public void addURL(URL url) {

        super.addURL(url);

    }

    

    public boolean isLoaded(URL url) {

     URL[] urls = getURLs();

     for(URL temp : urls) {

     if(temp.equals(url)) {

     return true;

     }

     }

     return false;

    }

}

코드 7.

코드 7 원하는 클래스  jar 런타임에 동적으로 로드하기 위한 클래스 로더이다. addURL 통해 jar  클래스를 로딩한다.


URL jarUrl = new File(toolsJarPath).toURI().toURL();

if(!ToolsJarLoader.getInstance().isLoaded(jarUrl)) {

ToolsJarLoader.getInstance().addURL(jarUrl);

}

Class cls = ToolsJarLoader.getInstance().loadClass("package.ClassName");

Object obj = cls.getConstructor(String.class).newInstance(arg1);

Method method = obj.getClass().getMethod("methodName");

method.invoke(obj);

코드 8.

코드 8 특정 위치에 있는 jar  클래스를 코드 7 있는 클래스로더를 이용하여 런타임에 동적으로 로딩하도록 한다로딩  해당 클래스를 만든다.

 

 

References

 

[1] http://javacan.tistory.com/3

[2] http://blog.naver.com/PostView.nhn?blogId=choigohot&logNo=40192701035

[3] http://javacan.tistory.com/entry/2

:

java class loader #1

Language/JAVA 2016. 8. 4. 15:06

Java 동적으로 클래스를 로딩하기 때문에 런타임에 모든 코드가 JVM 링크됩니다모든 클래스는 해당 클래스가 참조되는 순간에 동적으로 JVM 링크되며메모리에 로딩됩니다자바의 런타임 라이브러리([jdk path]/jre/lib/rt.jar) 역시 예외가 아닙니다이러한 동적인클래스 로딩은 자바의 클래스로더 시스템을 통해서 이루어지며자바가 기본적으로 제공하는 클래스로더는 java.lang.ClassLoader 통해서 표현됩니다. JVM 시작되면부트스트랩(bootstrap) 클래스로더를 생성하고 다음에 가장 첫번째 클래스인 Object 시스템에 로딩합니다.
런타임에 동적으로 클래스를 로딩한다는 것은 JVM 클래스에 대한 정보를 갖고 있지 않다는 것을 의미함으로서 JVM 클래스에 대한정보를 알지 못합니다따라서클래스로더는 클래스를 로딩할  필요한 정보를 구하고 클래스가 올바른지를 검사해야 합니다.


Load-time dynamic loading and Run-time dynamic loading

클래스를 로딩하는 방식에는 로드타임 동적 로딩(load-time dynamic loading) 런타임 동적 로딩(run-time dynamic loading) 있습니다. 

  public class HelloWorld {
     public static void main(String[] args) {
        System.out.println("
안녕하세요!");
     }
  }

코드 1.


로드타임 동적 로딩은 하나의 클래스를 로딩하는 과정에서 동적으로 클래스를 로딩하는 것을 말합니다코드 1 HelloWorld 클래스를 실행하면, JVM 시작되고앞에서 말했듯이 부트스트랩 클래스로더가 생성된 후에모든 클래스가 상속받고 있는 Object 클래스를 읽어옵니다 이후에클래스로더는 명령행에서 지정한 HelloWorld 클래스를 로딩하기 위해 HelloWorld.class 파일을 읽습니다그리고HelloWorld 클래스를 로딩하는 과정에서 필요한 클래스 java.lang.String java.lang.System 읽어옵니다  클래스는 HelloWorld 클래스를 읽어오는 로드타임에 동적으로 로딩됩니다.

 

class HelloWorld1 implements Runnable {
     public void run() {
        System.out.println("
안녕하세요, 1");
     }
  }
  class HelloWorld2 implements Runnable {
     public void run() {
        System.out.println("
안녕하세요, 2");
     }
  }

  public class RuntimeLoading {
     public static void main(String[] args) {
        try {
           if (args.length < 1) {
              System.out.println("
사용법: java RuntimeLoading [클래스 이름]");
              System.exit(1);
           }
           Class klass = Class.forName(args[0]);
           Object obj = klass.newInstance();
           Runnable r = (Runnable) obj;
           r.run();
        } catch(Exception ex) {
           ex.printStackTrace();
        }
     }
  }

코드 2.

 

런타임 동적 로딩은 클래스를 로딩할 때가 아닌 코드를 실행하는 순간에 클래스를 로딩하는 것을 말합니다.


코드 2에서 Class.forName(className) 파리미터로 받은 className 해당하는 클래스를 로딩한 후에  클래스에 해당하는 로딩한 클래스의 인스턴스가 아닌 Class 인스턴스를 리턴합니다그리고 Class 클래스의 newInstance() 메소드는 Class 나타내는 클래스의 인스턴스를 생성합니다예를 들어코드 3 java.lang.String 클래스의 객체를 생성합니다.

  Class klass = Class.forName("java.lang.String");
  Object obj = klass.newInstance();

코드 3.


따라서, Class.forName() 메소드를 실행하는 클래스에서는 Class.forName() 실행되기 전까지는 어떤 클래스를 참조하는    없습니다.


ClassLoader

클래스로더를 사용하기 위해서는 보통의 자바 클래스처럼 해당하는 클래스의 객체를 생성하고 객체의 특정 메소드를 호출하면 됩니다또한상속을 받아 클래스로드를 커스터마이징  수도 있습니다.

자바의 클래스로더는 클래스로더 딜리게이션 모델(ClassLoader Delegation Model)으로서 부모 클래스로더가 먼저 클래스를 로딩하도록합니다특정 클래스로더 클래스를 읽어온 클래스로더(부모 클래스로더)에게 클래스 로딩을 요청하는 것입니다


그림 1. ClassLoader Delegation Model[1]


그림 1 클래스로더간의 관계를 보여주고 있습니다 경우, NetworkClassLoader(가정) JarFileClassLoader(가정) 로딩하고, JarFileClassLoader 클래스는 AppClassLoader 로딩하였음을 보여줍니다, JarFileClassLoader NetworkClassLoader 부모 클래스로더가 되고, AppClassLoader JarFileClassLoader 부모 클래스로더가 되는 것입니다.

<자바에서 기본으로 제공하는 부트스트랩클래스로더 등에 관한 설명은 [2-3] 보면   있습니다.>


References

 

[1] http://javacan.tistory.com/3

[2] http://blog.naver.com/PostView.nhn?blogId=choigohot&logNo=40192701035

[3] http://javacan.tistory.com/entry/2

 

:

[Java] ClassLoader API

Language/JAVA 2016. 8. 4. 14:59

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를 포함한 이 하위 에러들이 발생할 수 있다. IllegalAccessErrorInstantiationErrorNoSuchFieldErrorNoSuchMethodError

추가적으로 구현체를 찾을 수 없는 native 메서드를 선언한 클래스에서는 UnsatisfiedLinkError가 발생할 수 있다. 


출처 - http://whiteship.tistory.com/2560

: