SSISO Community

시소당

ClassLoader의 비밀

[Java의 비밀] .class는 어떻게 하여 실행되는 것일까?

 

Java 프로그램을 작성하여 컴파일을 하면 항상 .class라는 파일이 생성된다. 먼저 .class 파일은 어떻게 메모리에 올라갈까? 많은 사람들의 생각과 달리 .class 파일은 메모리에 바로 올라갈 수 있는 파일이 아니다. 다시 말하면 이것은 Java byte 코드이지, C 언어에서 컴파일하고 난 후의 결과물과는 다르다는 것이다. 따라서, .class라는 Java byte 코드를 메모리에 올리는 역할을 하는 것이 필요한데 이런 역할을 하는 것이 ClassLoader이다.

참고로 ClassLoader는 java.lang 패키지에 있으므로 확인할 수 있을 것이다.

 

그러면 ClassLoader는 어떻게 .class 파일을 메모리에 올리는지 알아 보도록 하자. 아래 그림을 살펴 보도록 하자.

 

 

.class를 ClassLoader가 메모리에 올라가는 Class 객체로 만드는 것이다. 여기서 주의할 점은 Class 객체는 일반객체가 아님을 알아 두어야 한다.

아래 코드를 한번 살펴보자.

class Test {

    static String str=”a”;

    String s = “b”;

 

위의 Test 클래스를 다음과 같이 사용할 수 있을 것이다.

Test a = new Test();

위에서 new Test()가 바로 일반 객체가 된다. 이것을 어떻게 이해할 수 있을까?

아래 그림을 보자. 

 

즉, 메모리 내에서 Class 객체를 매개로 하여 일반 객체를 생성하게 되는 것이다. 우리가 일반적으로 알고 있는 static 변수는 어디에 있는 것일까?

바로 Class 객체에 존재한다. 그래서 우리가 static으로 선언하면 Global 변수로 사용할 수 있는 것이다. 즉, 일반 객체는 Garbage Collection의 대상이 되지만 Class 객체는 Garbage Collection의 대상이 되지 않기 때문에 그러하다.

 

이제, 실제 JVM이 어떻게 ClassLoader를 운용하는지 살펴 보도록 하자. JVM은 여러 개의 ClassLoader를 가지고 운용되는데, 이 때 사용된 모델이 Delegation Model이다.

 

 

위에서 노란색의 ClassLoader는 Class를 의미하고, 흰 색의 ClassLoader는 생성된 일반 객체를 의미한다. 물론 주황색은 Abstract Class이다.

?표 부분에 있는 Class는 Sun사에서 공식적으로 저것이 무엇이다 하는 언급이 없으므로 비어 있다. 물론 저 부분이 바로 최상위 ClassLoader 부분인데, 일반적으로 시스템 ClassLoader라고 부르곤 한다. 그래서 여기서도 그냥 System ClassLoader라고 부르기로 한다.

위의 그림에서 AClassLoader에 .class 파일을 class 객체로 만들라는 요청을 보내면, 자신에게 없을 경우 상위에 요청을 한다. 그래서 없으면 또 상위로 호출을 한다. 이렇게 하여 최상위까지 찾아서 없을 경우 ClassNotFoundException이 발생하게 된다.

 

좀 더 자세히 내부로 들어가 보도록 하자. 2개의 ClassLoader가 있을 경우에 다음과 같은 모양의 시퀀스 다이어그램으로 표현될 수 있다.

 

ClassLoader에 해당 Class 객체의 정보를 요구하는 경우 항상 loadClass 라는 메소드를 호출하게 된다. 따라서 모든 ClassLoader의 입구는 loadClass 메소드가 된다. loadClass를 호출하게 되면 최초로 findLoadedClass라는 메소드를 호출하는데, 이것은 메모리에 Class 객체가 존재하는지 먼저 확인하고 있으면 바로 Class 객체를 반환하게 되고, 없으면 상위 ClassLoader에게 의뢰하게 된다. 이러한 식으로 최상위인 System ClassLoader까지 거슬러 올라가서 메모리를 확인한다. System ClassLoader에서는 findLoadedClass 메소드를 통해서 아무런 Class 객체를 찾을 수 없다면 비로소 findClass 메소드를 호출하게 된다. findClass는 실제로 자신의 Classpath를 뒤져서 해당 .class 파일을 찾게 된다. findClass 메소드 내부에서 .class 파일을 찾게 되면 defineClass라는 메소드를 호출하게 되는데, 이는 protected 메소드이므로 시퀀스 다이어그램에서 표현하지는 않았다. defineClass 메소드는 찾은 .class 라는 자바 바이트 코드 파일을 Class 객체로 변환하는 작업을 하게 된다. 만약 findClass 메소드에서 .class 파일을 찾지 못하면 ClassNotFoundException을 발생시키며 바로 하위 ClassLoader에게 보낸다.

하위 ClassLoader에서는 위에서 수행한 findClass 메소드를 수행한다. 여기서도 발견을 못하면 또 하위로 ClassNotFoundException을 내려 보낸다. 계속해서 최하위까지 내려 갔을 경우 아무 것도 찾지 못했다면, 결국 사용자가 ClassNotFoundException을 받게 되는 것이다.

 

이것은 Java를 이용하여 개발하는 모든 사람에게 매우 중요한 개념으로 생각되는데, 그 동안 의심스러웠던 내용들에 대해 다음과 같은 사항을 고민해 보도록 하자.

 

1. Hot Deploy가 쉽지 않았던 이유가 무엇이었나?

여기에 대한 해답으로는 findLoadedClass에서 찾을 수 있을 것이다. 즉, 한번 메모리에 올라가 있는 Class 객체는 ClassLoader가 Garbage

Collection되기 전까지는 절대 내려오지 않기 때문이다. 만약 Java 파일을 수정하여 새롭게 컴파일하여 .class파일도 수정되었는데, 왜 변경된 로직으로 처리되지 않을까 하고 궁금하였다면 이것도 마찬가지로 이해할 수 있을 것이다.

 

2. Hot Deploy를 하려면 어떻게 해야 할 것인가?

ClassLoader를 Garbage Collection시켜야 한다. 그러나, 이 기능을 사용하게 된다면 상당한 성능 저하를 일으킬 수 있다는 것을 명심하여라.

아니면 ClassLoader를 작게 만들어서 ClassLoader의 계층구조를 깊게 가져가면 Garbage Collection시에 빠르게 처리될 수 있으므로 좋은 선택이 될 수 있다. 예를 들어 EJB의 경우는 컴포넌트별로 Garbage Collection이 쉬워서 이런 경우 유리해 진다. 또한 Java 1.4 이상의 경우 메모리 활용구조가 Eden 영역, Tenured 영역, Free 영역으로 구분되는데(1.3 이하는 Tenured 영역이 없다.) Eden 영역에서 Fast GC가 가능하므로 여러모로 Hot Deploy에 유리해 질 수 있다.

 

3. 같은 WAS 안에 있는 여러 개의 Application 끼리 상대방의 class 객체를 공유해서 사용하는 것이 쉽지 않은데 왜 그런 것인가?

이것은 아래 그림을 살펴 보자. App1과 App2 애플리케이션 2개가 WAS안에 존재하는데, App1의 경우는 1의 방향으로 Delegation이 이루어지고 있으므로 App2의 .class를 참조할 수가 없다. 따라서 이런 경우 참조하기 위해서는 RMI를 이용하거나 App2의 클래스패스를 App1에 등록시켜 주어야만 한다. 이런 경우 매우 지저분한 환경이 되고 말 것이다.

 

위의 그림은 이해를 돕기 위해서 간략히 그려졌으며, 실제로는 더 많은 ClassLoader가 있다는 것을 명심하여라.

 

4. 이런 방식은 상당히 불편할 것 같은데, 앞으로 변경될 가능성은 없는가?

Sun은 아직 Delegation Model을 변경할 계획이 전혀 없다. 왜냐하면, 이 방식이 가장 뛰어난 성능을 낼 수 있다고 믿기 때문이다.

 

 

참고 : Java Reflection in Action (Manning, 2005)

          http://java.sun.com

779 view

4.0 stars