Whiteship's Note


Class.forName()와 ClassLoader.loadClass() 차이점

Java : 2010.03.09 18:04


http://www.javaworld.com/javaworld/javaqa/2003-03/01-qa-0314-forname.html?page=1

일단 Class.forName() 메서드는 인자가 한 개 짜리인 것과 세 개 짜리인 것이 있습니다.

static Class<?>     forName(String className)
          Returns the Class object associated with the class or interface with the given string name.

static Class<?>     forName(String name, boolean initialize, ClassLoader loader)
          Returns the Class object associated with the class or interface with the given string name, using the given class loader.

1. 클래스를 로딩할 때 사용하는 클래스로더 차이

인자가 한개 짜리인 forName(String) 메서드는 클래스를 로딩할 때 사용하는 클래스로더가 저 코드를 실행하는 클래스로더가 됩니다. 하지만 ClassLoader.loadClass()를 사용하면 당연히 자기 자신을 사용해서 클래스 로딩을 실행하게 되죠. (그렇다고 해서 반드시 해당 클래스로더가 읽어온다는 보장은 없죠. 그 부모가 읽어올 수도 있고 클래스 패스에 없을 수도 있고 암튼 여기서 로딩한다는 건 로딩을 시도한다고 보시기 바랍니다.)

하지만 Class.forName(String, boolean, ClassLoader)를 사용하면 클래스 로더를 지정해 줄 수 있습니다.

2. 초기화

Class.forName(String) 메서드를 사용하면 곧바로 클래스의 static 초기화 블럭과 static 멤버 변수의 값을 초기화 합니다. 하지만 ClassLoader.loadClass()를 사용하면 해당 클래스를 처음으로 사용하기 전까지 초기화가 지연됩니다.

이것 역시 Class.forName(String, boolean, ClassLoader)의 두번째 인자값을 이용하여 조절할 수 있습니다.

- 클래스 초기화 에러

만약 Class.forName(String)을 사용해서 로딩할 때 static 영역에서 에러가 난다면 해당 클래스는 다시 로딩할 수가 없습니다. 특정 클래스로더가 일단 로딩한 클래스는 다시 로딩할 수가 없죠. 그래서 NoClassDefinitionFound 에러가 날 수도 있습니다.

이때는 해당 클래스로더 인스턴스를 버리고 새로 만들어야 하는데 그럴 때를 대비해 인자 세개짜리 forName을 쓰라는군요.

결국 forName()으로 클래스를 로딩할 떄는 별 개의 클래스로더를 쓰라는건데... 흠.. 그렇게 단순해 보이지가 않는데;; 머 클래스로더를 지정해 둔다고 해봤자. 보통 App CL로 읽어올테고 그럼 App CL 인스턴스를 버리라고?? 에이;; 그건 좀..

forName으로 읽어올 클래스를 클래스패스를 가지고 있으면서 parent로 위임하지도 않는 CL을 이용해서 forName으로 읽은 경우라면 뭐 괜찮을지도.. 어쨋거나 직접 통제가 가능한 클래스로더를 사용해야 겠군요.


top


톰캣 6.0 클래스로더 구조

Java : 2010.03.02 18:10


http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html

BootStrap CL: JVM 코어, 확장 라이브러리용 클래스 로더

System CL: $CATALINA_HOME/bin/bootstrap.jar와 CATALINA_HOME/bin/tomcat-juli.jar를 가지고 시스템 클래스 로더를 생성함. 일반적인 애플리케이션의 시스템 클래스 로더와는 동작 방식이 다름. 일반적인 애들은 시스템 클래스로더에서 CLASSPATH에 있는 것들을 로딩하지만, 톰캣의 스크립트(($CATALINA_HOME/bin/catalina.sh 또는 %CATALINA_HOME%\bin\catalina.bat)가 이를 무시해버림.

Common CL: $CATALINA_HOME/lib에 위치한 톰캣 내부 클래스와 웹 애플리케이션이 공통으로 참조할 라이브러리를 로딩한다. 

웹 애플리케이션 CL:  각각의 웹 애플리케이션당 해당 애플리케이션의 /WEB-INF/classes와 /WEB-INF/lib 디렉토리에 있는 클래스와 리소스를 로딩한다. 
SRV.9.7.2 Web Application Classloader

The classloader that a container uses to load a servlet in a WAR must allow the
developer to load any resources contained in library JARs within the WAR
following normal J2SE semantics using getResource. It must not allow theWAR to
override J2SE or Java servlet API classes. It is further recommended that the loader
not allow servlets in theWAR access to the web container’s implementation classes.
It is recommended also that the application class loader be implemented so that classes and resources packaged within the WAR are loaded in preference to classes and resources residing in container-wide library JARs.
위와 같은 서블릿 2.3 스펙에 따라 웹 애플리케이션 CL은 다음의 순서로 클래스를 로딩한다.

1. Bootstrap classes of your JVM
2. System class loader classes (described above)
3. /WEB-INF/classes of your web application
4. /WEB-INF/lib/*.jar of your web application
5. $CATALINA_HOME/lib
6. $CATALINA_HOME/lib/*.jar

1번은 웹 애플리케이션 CL의 최상위 CL인 BootStrap CL에 의해 로딩될 것이고, 거기서 못찾는다면
2번 System CL에 의해서 로딩 된다. 거기서도 못찾는거라면 일단 자바의 코어 라이브러리나 확장 라이브러리도 아니며 톰캣의 내부 클래스(톰캣 서버를 실행하는 클래스와 톰캣의 Commons logging API와 java.util.logging LogManager를 찾는게 아닐것이다. 그런 경우.
3번과 4번에서 웹 애플리케이션 CL 자신의 클래스패스에 있는 라이브러리를 찾아보고.. 거기에 없는 경우
5, 6번 Commons CL의 클래스패스에서 찾아본다.

클래스 로더의 동작 방식이 웹 애플리케이션이 아닌 경우가 웹 애플리케이션인 경우가 다르며, 이것 마저도 웹 서버 마다 다를 수 있으니 자신이 사용하는 컨테이너의 클래스로더 동작 방식을 알고 있어야겠다. 

흠.. 스프링의 TC 서버는 어떨지 궁금하다. 그것도 톰캣을 확장한 거니까 이것과 비슷하지 않을까. DM 서버는 왠지 완전히 다를 것 같다.
top


클래스 로더의 loadClass()와 findClass()

Java : 2010.02.25 13:30


참조: http://onjava.com/pub/a/onjava/2005/01/26/classloading.html?page=1

클래스와 데이타

모든 클래스는 first-class 자바 객체를 통해서 자신의 코드를 참조할 수 있다. 컴파일 할 때 컴파일러가 public static final class 라는 이름의 java.lang.Class 타입 필드를 추가해준다.

일단 한 번 클래스가 JVM으로 로딩되면, 같은 클래스는 다시 로딩 되지 않는다. 여기서 "같은 클래스"란 해당 클래스를 로딩한 클래스로더와 해당 클래스의 풀네임으로 구성된 식별자로 구분한다. 

예를 들어, WhiteshipClassLoader가 로딩한 springsprout 패키지의 Whiteship클래스는 (WhiteshipClassLoader, springsprout, Whiteship) 식별자를 가지게되고, SpringSproutClassLoader가 로딩한 springsprout 패키지의 Whiteship클래스는 (SpringSproutClassLoader, springsprout, Whiteship) 식별자를 가지기 때문에 별개의 클래스로 인식된다는 것이다.

클래스 로더

모든 클래스는 java,lang.ClassLoader의 인스턴스가 로딩한다.

부트스트랩 클래스 로더: java.lang.Object 같은 핵심 자바 클래스를 로딩한다. 런타임 클래스들은 JRE\lib\rt.jar에 들어있다. 접근이 안 됨. sout(String.class.getClassLoader())를 호출하면 null이 나온다. 

확장 클래스 로더: 핵심 자바 런타임 코드 이외의 확장 라이브러리를 java.ext.dirs 속성의 경로에 넣어둘 수 있다. ExtClassLoader는 java.ext.dirs에 위치한 모든 .jar 파일을 로딩한다.

애플리케이션 클래스 로더: AppClassLoader는 java.class.path 시스템 속성에 해당하는 경로의 모든 클래스를 로딩한다.

컨텍스트 클래스 로더: java.lang.Thread는 getContextClassLoader()라는 메서드를 가지고 있다. 이 메서드는 특정 쓰레드에서 사용중인 클래스로더를 반환해준다. 컨텍스트 클래스 로더는 쓰레드를 만들 때 사용한 클래스 로더이며, 기본적으로 parent 쓰레드의 클래스로더가 할당 된다. setContextClassLoader로 변경 가능하다. 

클래스로더 동작 방식

protected synchronized Class<?> loadClass
    (String name, boolean resolve)
    throws ClassNotFoundException{

    // First check if the class is already 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 invoke
            // findClass to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
   resolveClass(c);
    }
    return c;
}

java.lang.ClassLoader의 loadClass() 메서드 코드이며 이 클래스를 확장하는 클래스는 findClass()를 재정의하는 것이 기본적이다. java.lang.ClassLoader의 findClass()는 ClassNotFoundException을 던진다.

findClass()를 구현할 떄는 특정 소스에서 바이트 코드를 가져오거나, 소스를 바이트 코드로 생성하거나, BCEL(Byte Code Engineering Library)를 사용할 수도 있다. 암튼.. 그렇게 해서 바이트 코드를 가져온 다음 defineClass() 메서드를 호출해서 byte 배열을 Class 인스턴스로 변환해야 한다. 

defineClass() 메서드 호출이 중요한데, 이 것을 호출한 클래스 로더가 바로 해당 클래스의 defining 클래스 로더가 되는 것이며 이 defining 클래스로더가 다를 경우 다른 클래스로 인식하게 된다. Whiteship.class.getClassLoader()를 실행했을 때 반환되는 클래스 로더가 바로 defining 클래스 로더다.

findClass 구현 예제 코드

    public Class findClass(String name)throws
        ClassNotFoundException{

        byte[] classBytes = findClassBytes(name);
        if (classBytes==null){
            throw new ClassNotFoundException();
        }
        else{
            return defineClass(name, classBytes,
                0, classBytes.length);
        }
    }


top


자바 클래스로더 입문 퀴즈 정리

Java : 2010.02.23 22:30


  1. 21:15:40 [ClassLoader 퀴즈 끝] SpringSprout와 WhiteshipFactory가 참조하는 Whiteship은 누구인가.
  2. 2010/02/22 [ClassLoader 퀴즈 6] SpringSprout는 과연 Whiteship의 이름을 알 수 있을까? (2)
  3. 2010/02/22 [ClassLoader 퀴즈 5] Whiteship은 언제 로딩 될까? (2)
  4. 2010/02/21 [ClassLoader 퀴즈 4] SpringSprout가 알고 있는 Whiteship은 누구인가? (9)
  5. 2010/02/19 [ClassLoader 퀴즈 3] Whiteship은 Whiteship일까 아닐까? (2)
  6. 2010/02/19 [ClassLoader 퀴즈 2] Whiteship은 내가 데려왔다!! (5)
  7. 2010/02/19 [ClassLoader 퀴즈 1] Whiteship은 대체 누가 데려온 것일까? (8)

입문 과정 퀴즈를 푸는데 필요한 학습 자료
- ClassLoader APi, 소스 학습
- URLClassLoader API, 소스 학습
- http://docs.jboss.org/jbossas/jboss4guide/r1/html/ch2.chapter.html#d0e2314

과제
- 위 Jboss 문서 2.2.2를 참조하여 클래스 로딩 관련 주요 예외 3종 세트 재현하기
- ClassCastException
- IllegalAccessException
- LinkageError

파이팅입니다.
top