Computer 그리고 Developer/dW

[dW] 클래스 로딩 문제 분석하기

버리야 2008. 3. 4. 10:16
반응형
클래스 로딩 문제 분석하기에 좋은 글이 있어서 올립니다
원문이 나온지는 좀 되었지만, 한번 읽어보기에 좋은 것 같습니다.
아래 내용은 저혼자 공부하면서 내용을 잠깐 정리해 본 것입니다.
Part 4로 이루어져 있기에 정리한 내용보다 더 방대한 내용이 담겨져 있습니다. ^^


클래스 로딩 문제 분석하기, Part 1: 클래스 로딩과 디버깅 툴 소개 (한글)
클래스 로딩 문제 분석하기, Part 2: 기본적인 클래스 로딩 예외(Exception) (한글)
클래스 로딩 문제 분석하기, Part 3: 특이한 클래스 로딩 문제 해결 (한글)
클래스 로딩 문제 분석하기, Part 4: 교착 상태와 제약 조건 (한글)

원문 시리즈
http://www.ibm.com/developerworks/views/java/libraryview.jsp?search_by=demystifying+class+loading+problems

Demystifying class loading problems, Part 1: An introduction to class loading and debugging tools
Demystifying class loading problems, Part 2: Basic class loading exceptions
Demystifying class loading problems, Part 3: Tackling more unusual class loading problems
Demystifying class loading problems, Part 4: Deadlocks and constraints


그 외 ClassLoader와 관련된 글
Java programming dynamics, Part 1: Classes and class loading

<Part 1 : 클래스 로딩과 디버깅 툴 소개>

클래스 로더는 클래스들을 Java Virtual Machine (JVM)에 로딩하는 일을 담당한다. 단순한 애플리케이션들은 자바 플랫폼에 내장되어 있는 클래스 로딩 장치를 사용하여 클래스들을 로딩한다. 보다 복잡한 애플리케이션들은 고유의 클래스 로더를 정의하기도 한다. 어떤 종류의 클래스 로더를 사용하든지 간에, 클래스 로딩 과정 동안 문제가 생길 수 있다. 이 같은 문제를 피하려면, 클래스 로딩의 구조를 이해해야 하여야 하며, 이를 통해, 문제가 발생하면 진단 기능과 디버깅 기술이 문제 해결에 도움이 될 것이다.

클래스 로딩은 로딩(loading), 링크(linking), 초기화(initializing) 단계로 나뉜다.

로딩 단계는 필요한 클래스 파일을 배치하고(각각의 classpath를 통해 검색함) 바이트코드로 로딩하는 단계이다. JVM의 로딩 프로세스는 클래스 객체에 매우 기본적인 메모리 구조를 제공한다.

링크(Linking)는 세 단계 중에서 가장 복잡한 단계이다. 다음과 같이 세 개의 주요 단계들로 나뉜다.

초기화(initialize) 단계 동안, 클래스 내에 포함된 정적 이니셜라이저(initializer)들이 실행된다. 이 단계의 끝에 가서는 정적 필드들이 기본 값으로 초기화 된다.

<Part 2: 기본적인 클래스 로딩 예외(Exception)>

ClassNotFoundException
ClassNotFoundException은 클래스를 로딩하려는 분명한 시도가 실패할 경우에 던져진다.

NoClassDefFoundError
자바 가상 머신(Java virtual machine) 또는 ClassLoader 인스턴스가 클래스 정의 중 로딩을 시도하고(정상적인 메소드 호출의 일환으로, 또는 새로운 식을 사용하여 새로운 인스턴스를 생성하는 것의 일환으로), 이 클래스에 대한 어떤 정의도 찾을 수 없을 때 예외(Exception)가 던져진다.

ClassCastException
이는 코드가 인스턴스가 아닌 것의 하위 클래스로 객체를 캐스팅(cast)할 때 생기는 예외(Exception)이다.

UnsatisfiedLinkError
JVM이 native로 선언된 메소드의 적절한 기본 언어 정의를 찾을 수 없을 때 발생한다.

ClassCircularityError
클래스나 인터페이스는 자기 자신의 하위 클래스나 하위 인터페이스가 되므로 클래스나 인터페이스는 로딩될 수 없다.

ClassFormatError
요청 받은 컴파일 된 클래스 또는 인터페이스를 지정하는 바이너리 데이터가 잘못 구성되었다.

ExceptionInInitializerError
  • 이니셜라이저가 E라는 예외(Exception)를 갑자기 던지고 종료하고, E의 클래스가 Error 또는 이것의 하위 클래스들 중 하나가 아니라면, ExceptionInInitializerError 클래스의 인스턴스가 E라는 인자와 함께 생성되어 E를 대신하여 사용된다.

  • JVM이 ExceptionInInitializerError 클래스에서 새로운 인스턴스를 만들기를 시도하지만 Out-Of-Memory-Error가 발생하여 그렇게 할 수 없을 경우, OutOfMemoryError 객체가 대신 던져진다


    <Part 3: 특이한 클래스 로딩 문제 해결>

    같은 듯한 다른 classpath문제

    classpathh에 /(slash)로 끝나는 경로와 slash가 없는 경로의 차이점

    예제에서의 결과

    file://C:/CL_Article/ClasspathIssues/cp/
    Class Z loaded.
    file://C:/CL_Article/ClasspathIssues/cp
    java.lang.ClassNotFoundException: Z
    at java.net.URLClassLoader.findClass(URLClassLoader.java:376)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:572)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
    at ClasspathTest.run(ClasspathTest.java:28)
    at ClasspathTest.main(ClasspathTest.java:36)


     URLClassloader는 약간 다른 매개변수를 전달한다. 첫 번째 클래스 로더 cl1에는 뒤에 /가 붙은 classpath가 주어진다. 두 번째 클래스 로더인 cl2의 classpath에는 /가 붙지 않는다. 클래스 로더는 /가 붙지 않은 경로는 JAR 파일을 참조하는 것으로 간주하기 때문에 이는 매우 중요한 차이이다. /로 끝나는 경로들만 디렉토리를 참조하는 것으로 간주된다.

    이러한 문제를 해결하는 방법은 각 경로의 끝에 /가 꼭 붙도록 해야 한다.


    가비지 컬렉션과 직렬화와 관련한 문제들

    가비지 컬렉터는 클래스 로더와 긴밀하게 상호 작동한다. 무엇보다도, 컬렉터는 클래스 로더 데이터 구조를 검사하여 어떤 클래스가 활성(live) 상태인지를 파악한다. 다시 말해서, 가비지 컬렉션 대상이 아닌지를 결정한다. 이는 예상치 못한 문제로 이어질 수 있다.

    클래스 로더가 참조되지 않을 때, 로딩되는 클래스들은 가비지 컬렉션 될 수 없다. ObjectStreamClass lookup table에서 SerializationClass 클래스에 대한 활성 참조가 있기 때문이다. ObjectStreamClass는 근원 클래스이기 때문에 가비지 컬렉션 대상이 될 수 없다. lookup table은 ObjectStreamClass의 정적 필드로부터 참조되고 이것의 인스턴스 보다는 클래스에 저장된다. 결국, SerializationClass에 대한 참조는 JVM의 수명 기간 동안 존재하며, 클래스는 가비지 컬렉션 대상이 될 수 없다. 중요한 것은, SerializationClass는 정의하는 클래스 로더에 대한 참조를 갖고 있기 때문에 완전하게 참조되지 않을 수는 없다.

    이러한 문제를 해결하려면, 직렬화 되지 않은 클래스들은 가비지 컬렉션 될 필요가 없는 클래스 로더(예를 들어, 시스템 클래스 로더)에 의해 로딩되어야 한다.

    <Part 4 : 교착 상태와 제약 조건>

  • 교착 상태 :

    두 개의 쓰레드가 각각 다른 클래스 로더에 잠금을 갖고 있고, 두 개의 쓰레드 모두 다른 쓰레드가 갖고 있는 잠금을 기다릴 때 발생한다. 두 개의 쓰레드는 다른 한쪽의 클래스 로더에 대한 잠금을 무한정 기다리기 때문에 교착 상태에 빠지게 된다. 이러한 교착 상태는 일반 델리게이션 모델이 우회(bypass)될 때 멀티 쓰레드 환경에서 발생할 수 있다.

    제약 조건 :

    두 개의 클래스 로더들이 같은 이름을 가진 다른 클래스들(다시 말해서, 다른 바이트코드)를 로딩하면, 클래스 로더 제약 조건이 이들 간 유형 미스매치(mismatch)가 없도록 보장해 준다.
    JVM 스팩에 따라, 다음과 같은 네 가지 조건들을 갖고 있으면 클래스 로더 제약 조건을 위반한 것이다.
    • N이라고 하는 Class C의 초기화 로더처럼 JVM에 의해 기록된 로더 L이 존재한다.

    • N이라고 하는 클래스 C'의 초기화 로더처럼 JVM에 의해 기록된 로더 L'이 존재한다.

    • 부과된 제약 조건에 의해 정의된 대등 관계가 N L = N L'를 함축하고 있다.
    • C != C'

    반응형