A class loader is an object that is responsible for loading classes.
이 구문은 JDK API 문서 중 ClassLoader에 대한 부분 중 가장 먼저 나오는 구문입니다.
이 구문과 같이 ClassLoader는 Java class들의 loading 기능을 담당하는 class 입니다.
이번 포스트에서는 모든 class에 대해 instance를 생성해 주는 ClassLoader에 대해 알아보도록 하겠습니다.

위의 문구에서와 같이 모든 class는 ClassLoader를 통하게 되는데요. 이는 Java에서 class는 ClassLoader를 통해 물리적인 class파일을 JVM으로 loading하게 됩니다.
또한 ClassLoader는 class를 JVM으로 loading 하기 위해서 정해진 경로(흔히 classpath임)에서 해당 class 파일을 찾아 loaging 작업을 수행하게 됩니다.
이때 ClassLoader는 class 파일이 structurally well-formed 여부를 확인하는 작업 및 보안 관련된 작업을 수행하게 됩니다.
물론 Custom ClassLoader를 통해 더 많은 작업을 수행할 수도 있습니다.
Java에서 기본적으로 제공하는 ClassLoader를 상속받아 다양한 Custom ClassLoader를 생성할 수 있는데요.
이러한 Custom ClassLoader를 통해 구현한 대표적인 기능 중의 하나가 Hot-Deploy라 할 수 있습니다. 
Java 프로세스에서의 Hot-Deploy는 프로세스의 재기동 없이 변경된 모듈을 적용하는 기능입니다.
이러한 기능은 Custom ClassLoader내에 변경 여부를 확인하는 로직과 변경 시 재로딩하기 위한 기능이 구현되어 있는 것이라고 할 수 있습니다.
Custom ClassLoader를 사용하게 되면 class 로딩 시점에 다양한 기능을 구현하고 활용할 수 있습니다.
예를 들어 loading하고자 하는 class 파일이 없거나 소스 파일이 변경되었을 경우 해당 소스파일을 찾아 컴파일한 후 class파일을 loading하는 기능(jsp의 Hot-Deploy 기능과 유사)을 구현할 수도 있습니다.

그럼 Custom ClassLoader가 작동하는 방식에 대한 설명을 WAS의 jsp 모듈을 로딩 및 실행하는 경우를 통해 설명드리겠습니다.


위의 그림과 같이 사용자의 jsp 처리 요청이 발생할 경우 처리 Thread들이 해당 요청을 처리하기 위해 할당되며, 
해당 Thread가 jsp에 대한 instance의 loading 일자와 실제 물리적인 jsp 파일의 변경 일자를 비교하게 됩니다. 
비교결과 파일이 변경되었다면 변경된 jsp 파일을 읽어들여 .java 파일로 변경하고 class로 컴파일 하게됩니다.
컴파일된 class 파일을 instance화하여 구버전의 instance와 바꿔치기를 수행합니다. 이렇게 되면 사용자 요청은 변경된 jsp 모듈을 수행할 수 있게 됩니다.

그럼 ClassLoader에 대해 본격적으로 설명드리기 전에 Java 프로그램(JVM)의 LifeCycle에 대해 알아보고, ClassLoader가 어느 부분에서 관여가 되는지를 확인해 보도록 하겠습니다.


위의 그림과 같이 Java 프로그램이 실행될 때는 JVM Start-up, Loading, Linkig(Verification, Preparation, Resolution), Initialization, New class creation, Finalization, Unloading, JVM Exit 단계의 Life Cycle을 가지게 됩니다.
이 중 ClassLoader가 관여하는 단계는 JVM Start-up, Loading, Linking, Initialization, New class instance creation 부분이 되겠습니다.

그럼 이제 본격적으로 ClassLoader에 대해 알아보기위해 ClassLoader가 class를 loading하기 위한 내부동작(Method 단위) 방식에 대해 알아 보도록 하겠습니다.


위의 그림과 같이 ClassLoader는 Parent/Child 형식의 계층 구조를 가지게 됩니다. 그리고 이러한 계층 구조를 통해 load된 class 및 load할 class들을 찾게 됩니다.
시스템 운영중에 발생할 수 있는 각종 ClassLoader와 관련된 문제들이 이러한 계층 구조에서 비롯된다고 할 수 있습니다.
예를 들어 class를 찾지 못하는 문제같은 경우는 계층 구조의 어느 ClassLoader에서 class를 load했느냐와 해당 ClassLoader가 어떤 classpath를 가지고 있느냐에 깊은 연관이 있습니다.

ClassLoader는 class를 load하기 위해 각종 수행해야 하는 절차와 관련된 메소드들이 있습니다.
가장 먼저 class를 load하기 위해 수행되는 메소드는 loadClass()입니다. 이는 class를 instance하기 위한 class의 메타 정보를 찾기 위해 가장 첫번째로 수행하는 메소드입니다.
그 다음으로 findLoadedClass()가 수행이 되는데요. 이 메소드는 이름과 같이 load할 class가 현재 load 되어 있는지의 여부를 확인하는 역할을 합니다.
findLoadedClass() 에서 load할 class가 이미 load된 상태라면 바로 return되고 해당 class 메타 정보를 이용하여 instance가 생성됩니다.
그러나 load할 class가 load가 되지 않았다면 ClassLoader는 parent의 loadClass()를 호출하게 됩니다.
parent ClassLoader는 child ClassLoader가 했던것처럼 loadClass()에서 findLoadedClass() 메소드를 호출하여 load할 class가 현재 ClassLoader에 load되어 있는지 여부를 확인합니다.
child ClassLoader와 똑같이 load할 class가 이미 load된 상태라면 바로 return되고 해당 class의 instance가 생성됩니다.
그러나 이번에도 찾고자 하는 class가 load되지 않았다면 ClassLoader는 또 다른 parent ClassLoader의 loadClass()를 호출하게 됩니다.
이때 더이상 parent ClassLoader가 존재하지 않는다면 findBootstrapClass() 메소드가 호출되어 Bootstrap ClassLoader의 findBootstrapClass()가 호출되어 Bootstrap ClassLoader에서 load할 class를 찾게 됩니다.
이러한 방식으로 load할 class가 이미 load되어 있다면 load된 class 메타 정보로 해당 class의 instance를 생성하게 됩니다.
그런데 load할 class가 모든 ClassLoader상에서 load되어 있지 않다면 최상위 ClassLoader(Bootstrap ClassLoader)에서 부터 class파일을 load하기 위해 classpath의 경로에서 물리적인 class 파일을 찾게됩니다.
최상위 ClassLoader에서 classpath상에서 class를 못찾게되면 다음 child ClassLoader에서 찾게되면 그렇게 찾아 내려오다 class가 찾아 지는 ClassLoader에서 class가 load됩니다.
만약 모든 ClassLoader상에서 물리적인 class파일을 못찾게 되면 ClassNotFoundException 이 발생하게 됩니다.

자! 그럼 ClassLoader의 가장 핵심인 loadClass() 메소드에 대해 살펴보도록 하겠습니다.


위의 그림과 같이 ClassLoader의 loadClass()에는 위에서 제가 복잡하고 장대하게(??) 설명한 내용이 몇줄로 구현되어 있습니다.
보시면 별 무리없이 이해하시리라 생각됩니다.

그럼 여기서 앞쪽에 잠깐 언급했던 ClassLoader의 계층구조에 대해 살펴보도록 하겠습니다.
이러한 계층구조로 인해 class를 찾지 못하거나 변경이 발생하지 않는 경우가 실제로 발생하는 사례들이 많이 있습니다.


위의 그림은 Java에서 사용되는 일반적인 ClassLoader의 계층구조입니다.
사용하는 환경에 따라 조금씩 다를수는 있지만 거의 표준적으로 위와 같은 구조를 가지고 있습니다.
가장 상위부터 Bootstrap ClassLoader -> Extensions ClassLoader -> Application ClassLoader -> Custom ClassLoader 순의 계층으로 구성됩니다. 좌측의 화살표는 class를  loading하기 위해 class파일을 찾는 순서입니다.
각 계층별로 동일한 package의 class파일이 있다면 가장 상위의 ClassLoader에 의해 class파일이 load됩니다.

다음은 ClassLoader의 계층 구조를 확인하기 위한 간단한 코드와 여러 환경에서 수행한 결과입니다.


위의 코드는 현재 Thread의 ClassLoader와 그 parent의 ClassLoader들을 계속 출력하는 것이며, 그리고 이 프로그램을 일반 어플리케이션과 일부 WAS에서 jsp로 수행한 결과입니다. 
ExtClassLoader와 AppClassLoader는 Java Vendor에게 제공하는 기본 ClassLoader이며 그 상위로는 각 WAS 벤더별로 Custom ClassLoader가 구현(StandardClassLoader나 ChangeAwareClassLoader 등)되어 있는 것을 알 수 있습니다.

이러한 ClassLoader로 인해 발생하는 문제에 대해 확인해 보고 예제를 통해 해결하는 방법을 알아 보도록 하겠습니다.
먼저 ClassNotFoundException이 발생하는 경우입니다.
ClassNotFoundException은 load할 class를 ClassLoader가 찾지 못할 경우 발생합니다. 이 오류는 ClassLoader와 관련하여 가장 많이 발생하는 문제입니다. 이 문제는 손쉽게 해결되는 경우도 있지만 간혹 해결에 어려움을 겪기도 합니다.

다음으로는 변경된 class가 반영되지 않을 경우입니다.
이 경우는 class를 변경하여 저장한 후 프로그램을 재수행했을때 프로그램 수행에는 문제가 없지만 변경된 내용이 반영되지 않고 기존의 class가 수행되는 것입니다.
이것은 대부분 잘못된 class 파일 관리 정책으로 인해 발생하는 경우가 많으며, 계층적으로 이루어진 ClassLoader상에서 잘못된 경로에 class 파일들이 위치할 경우에 발생할 수 있습니다.

다음으로는 Heap 중 Permanent 영역의 Full이 발생하는 경우입니다.
이 경우는 class의 메타 정보를 저정하는 Permanent 영역이 정상적으로 해제되지 않고 지속적으로 증가하여 Permanent 영역의 부족으로 OutOfMemoryError가 발생하는 경우입니다.
Custom ClassLoader의 잘못된 구현으로 발생할 수도 있으나, class간의 관계에서 참조값이 해제되지 않아 발생하는 경우가 많습니다. 또는 Permanent 영역이 실제 사용하는 class 크기보다 작게 설정되어 발생할 수도 있습니다.

다음으로는 NoClassDefFoundError가 발생하는 경우입니다.
이 경우는 class 파일이 잘못된 구조(malformed)로 인해 발생하는 문제이며, 보통 쉽게 해결되는 문제입니다.

그럼 ClassNotFoundException이 발생하는 문제를 예제를 통해 분석해 보도록 하겠습니다.
이번 예제는 EJB와 Web Application 간의 ClassLoader 관계로 인해 발생하는 ClassNotFoundException에 대한 문제입니다.
일부 WAS들은 class 공유를 위해 ClassLoader에 두가지 mode로 운영할 수 있는 기능을 제공합니다.
두가지 mode는 다음과 같습니다.


위의 그림과 같이 SHARED와 ISOLATED로 두가지 mode가 있으며 두 mode는 ClassLoader의 계층구조가 다른 것을 알 수 있습니다.
SHARED  모드일 경우는 Servlet ClassLoader의 class들이 EJB ClassLoader의 class를 참조할 수 있는 장점이 있으나 ClassLoader 간의 class 공유로 인한 구성의 복잡성 문제가 발생할 수 있습니다.
그에 비해 ISOLATED 모드는 Servlet ClassLoader와 EBJ ClassLoader간에 class들의 공유되지 않아 class 참고를 위해 중복 배포해야 하는 관리의 어려움이 있습니다. 예를 들어 EJB의 Remote Interface class를 servlet에서 참조하기 위해 Servlet ClassLoader가 access되는 위치에 배포해야 합니다.
그러나 독립성이 보장된다는 장점으로 ISOLATED 모드가 요즘은 대세인 분위기 입니다.

다음은 변경된 class가 반영되지 않는 문제를 예제를 통해 분석해 보도록 하겠습니다.
이 경우는 잘못된 class 파일 관리 정책으로  인해 ClassLoader의 경로 상에 의도한 경로 외에 다른 경로의 class가 사용되는 경우라 할 수 있습니다.
이러한 문제에 대한 예제는 아래 그림에서와 같습니다.


위의 그림과 같이 동일한 class 파일이 Application ClassLoader의 classpath와 Servlet ClassLoader의 WEB-INF/classes 내에 같이 존재할 경우 class는 Application ClassLoader의 class가 반영됩니다.
그러나 실제 class들이 사용되는 것이 servlet 이여서 Servlet ClassLoader의 WEB-INF/classes에 변경하여 저장하더라도 해당 class가 반영되지 않는 문제가 발생합니다.
이 경우 관리자가 class들의 사용 용도와 레벨을 정하여 정확한 위치에 배포하는 정책이 필요합니다.
다음은 class가 load된 위치를 확인할 수 있는 간단한 프로그램입니다. 실제 어디에 있는 class인지를 확인할 수 있는 유용한 프로그램입니다.


다음은 Permanent 영역의 Full로 인해 발생하는 문제를 예제를 통해 분석해 보도록 하겠습니다.
이 부분은 이미 Heapdump 분석 포스트에서 이미 언급한 부분이여서 해당 포스트(http://blog.naver.com/bumsukoh/110125469644)를 참고하시기 바랍니다.

다음은 NoClassDefFoundError가 발생하는 문제에 대해 예제를 통해 분석해 보도록 하겠습니다.
이 문제는 대부분 class의 잘못된 구조로 인해 발생하는 문제이며 대부분 재컴파일을 통해 class를 재생성하게 되면 해결되는 문제들입니다.
아래 그림은 class 이름을 임의로 변경하여 발생하는 NoClassDefFoundError의 예제입니다.


위의 그림과 같이 class이름을 임의로 변경 후 프로그램을 수행한 결과 NoClassDefFoundError가 발생하는 것을 알 수 있습니다.
재컴파일 후에 프로그램을 재수행하면 다시 정상적으로 동작하게 됩니다.

지금까지 ClassLoader에 대한 설명과 발생 가능한 문제점 및 해결 방법을 알아 보았습니다.
다음 시간에는 이번 트러블슈팅 가이드 시리즈의 마지막 포스트인 JNI 문제 분석에 대해 설명드리도록 하겠습니다.
다들 다음 포스트때 가지 즐거운 시간 되시기 바랍니다.


출처 -  http://blog.naver.com/bumsukoh?Redirect=Log&logNo=110127431857&from=postView

'Development > Java' 카테고리의 다른 글

java - JVM 메모리 및 옵션  (0) 2012.02.02
Heap dump란?  (0) 2012.02.02
BTrace  (0) 2012.02.02
jstat 유틸을 이용하면 JVM상태를 상세하게 모니터링 할 수 있다.  (0) 2012.02.02
java.lang.OutOfMemoryError: PermGen space  (0) 2012.02.02
Posted by linuxism
,