IT모아

자바 로드타임 로딩 및 런타임 로딩 이해하기(ClassLoader) 본문

Java(자바)

자바 로드타임 로딩 및 런타임 로딩 이해하기(ClassLoader)

아롱사태남 2016.10.20 14:48

JVM과 Class Loader의 이해

 

 

JVM(Java Virtual Machine)은 Machine 즉 하나의 작은 컴퓨터 머신으로 자체적인 명령 집합을 가지고 있으며 Memory 영역을 나누어 알아서 관리를 하고 있다는 근거에서 이러한 이름을 가지게 되었다

 

JVM은 크게 3가지 부분으로 나눌 수 있다. class file 및 libraries를 로딩하는 클래스 로더(Class Loader subsystem), 로딩된 class를 수행하기 위한 실행시간 데이터 영역(Runtime data area), 또한 이 Class를 실행하는 실행엔진(Excecution engine)이  그것들이다. 

Runtime data area**는 다시 Method area(메소드 영역), Heap(힙 영역), Java stack(스택 영역), Pc register, native method stack(Native 메소드 스택)의 5가지 영역으로 나누어 진다. 

 

 

 

 

 

 

 

Class Loader란? 

Class Loader란 자바의 큰 장점중의 하나로, Compile Time이 아닌 Run Time에 Class를 로딩할 수 있게 해주는 기술이다.

**아래 참고 : 로드타임 동적 로딩(load-time dynamic loading)과 런타임 동적 로딩(run-time dynamic loading)

 

 

Class Loader의 특징으로는

- Hierarchical 

   계층적 구조를 가지도록 생성이 가능하다. 부모 클래스 로더에서 자식 클래스로더를 가지는 것과 같은 형태를 말한다. 그래서 클래스로더는 Bootstrap <- Extension <- Application의 구조를 가지게 된다.   

 

- Delegate load request

   로드 요청을 위임한다는 의미로 위와 같이 Class Loader가 계층적 구조를 가지고 있기 때문에 일정 시점에 Class Loading 요청을 받을때, 상위 class loader가 Loading한 class가 우선권을 가진다. 예를 들어 Class Loader가 부모로 부터 CL1 - CL2 - CL3 순서로 생성되어 있다고 가정하자 애플리케이션이 Class Loader CL3로 요청하면 CL3는 CL2로, CL2는 CL1으로 즉 Class 로딩의 우선 순위는 부모 클래스가 가지고 있다. 만약 요청받은 Class 가 없을 경우 아래 레밸의 class loader가 로딩하게 된다.

 

- Have Visibility Constraint 

   상위 Class Loader를 먼저 참조하는것에 이어서, Class Loader는 일종의 scope rule을 제공하는데, Child Class Loader는 Parent Class Loader의 Class를 Delegation load request를 이용하여 찾을 수 있지만, 그 반대로 Parent가 Child가 Loading한 Class를 사용할 수 는 없다. 또한 Parent의 같은 Level의 Child Class Loader는 서로 로딩한 Class를 사용할 수 없게 되어 있다.

 

- Cannot unload classes

   Class Loader에 의해서 Loading된 Class들은, unload될 수 없다.. Class Loader에는 Class Unloading기능이 없다. 그래서 이 unloading 기능을 우회적으로 구현하는 방법은 Class를 로드한 Class Loader 자체를 삭제하고, 새로운 Class Loader를 만들어서 다시Class 를 로드하면, reload되는 것처럼 작동하는것이 된다.

 

 

 

 

Class loader의 위계구조는 위의 그림과 같다. 쉽게 말해 Bootstrap-Extension-Application의 구조를 가지게 된다. Class loader들이 class를 로딩하게 되면 하나의 delegation parent와 함께 생성되고 Cache-Parent-Self의 순서로 해당 class를 확인하게 된다. Class loader가 로딩을 하게 될 때 가장 먼저 하게 되는 일은 해당 Class가 이미 로딩이 되어 있는지를 확인하는 것이다. 만약 로딩이 되어 있다면 메모리에 Cache되어 있을 것이고 이 경우 해당 class를 반환하게 된다. 만약 로딩이 되지 않았다면 부모(parent)에게 class를 로딩하게 위임을 하게 되고 부모가 Class를 로딩할 수 없다면 자신(self class loader)이 클래스를 탐색하게 된다. 

 

이렇게 로딩되는 경우 class를 로딩할 확률은 delegation model의 상위로 갈수록 높아진다. 가장 상위에 있는 bootstrap class loader는 JVM의 수행을 위한 핵심적인 class만을 로딩하게 되고 가장 기본적인 class들의 버전이 정확하게 일치하는지를 확인한다. 또한 각 class가 누구에 의해 로딩이 되었는지에 대한 정보도 제공하고 있다. (참고로 이 정보는 자신을 포함한 상위의 class loader가 로딩한 것만 확인이 가능하고 자신의 하위의 loader가 로딩한 것에 대한 정보는 알 수 없다.) 

 

Bootstrap loader는 VM의 일부로 구현되기 때문에 다른 class loader와는 달리 자바 코드로 인스턴스화 할 수 없다. 이 class loader는 JVM의 수행을 위한 핵심 시스템 class를 로딩하는 데 일반적으로 $JAVA_HOME/jre/lib에 있는 JAR파일들이 그 대상이 된다. 이를 변경하기 위해서는 -Xbootclasspath옵션을 사용하면 된다. 


Extension class loader는 Bootstrap의 바로 하위에 있는 class loader이다. 주로$JAVA_HOME/ jre/lib/ext에 위치한 JAR파일이 그 대상이 된다. 

 

Application class loader는 $CLASSPATH에 지정된 경로에서 class를 로딩한다. 이 class loader는 user-defined class loader의 부모가 된다. 

 

결국 delegation model에 따라class를 로딩하게 때문에 그리고 각 loader가 class를 로딩하는 위치는 정해져 있기 때문에 class loading시 가장 먼저 찾게 되는 경로는 $JAVA_HOME/jre/lib가 되고 그 다음으로는 $JAVA_HOME/ jre/lib/ext, $CLASSPATH의 순서가 된다. 그러므로 가급적 사용자가 정의한 class의 경우는 Core class가 위치한 경로를 피하는 것이 좋다. 







 

 

** 참고 

로드타임 동적 로딩(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();

  }

 


 

 

 

** 참고

런타임 데이터 영역(Runtime data area)

- 클래스 로더에서 준비해서 여기서 이제 수행하면서 쓸 데이터를 보관한다.

 

1. 메소드 영역

클래스의 정보(클래스 형정보)를 저장하고 프로그램이 수행되는 동안 클래스의 정보(클래스 형정보) 참조하는곳

저장되는 것은 로딩된 클래스의 정보, 멤버변수정보, 메서드 정보 ,static 변수(클래스변수),상수

Import된 클래스들이 로드된 곳

이 영역은 JVM에서 실행되고 있는 모든 쓰레드(프로그램)에 의해 공유된다

JVM은 여러 개의 쓰레드가 메소드를 정상적으로 사용하기 위한 동기화 기법을 제공한다

 

2.힙 영역

프로그램 상에서 데이터를 저장하기 위해 동적으로(실행시간에)할당하여 쓸수 있는 메모리영역

자바 프로그램은 프로그램 실행 new 연산자를 사용하여 객체를 동적으로 생성(단 메소드 영역에 로드된 클래스만 생성가능)

이 영역도 유일한 공간으로 여러 쓰레드가 공유한다.

더더욱 이영역은 메모리해체를 할수 없다 오로지 쓰레기 수집가만이 가능한다

메소드영역의 클래스 형정보를 참고로해서 인스턴스를 생성하는곳이다.

 

3. 스택영역

메소드가 호출될때마다 스택 프레임이라는 데이터 영역이 생성되며 , 이것이 쌓여 스택을 구성한다.

수행되는 메소드 정보, 로컬변수,매개변수,연산중 발생하는 임시데이터의 등이 저장

위와 같은 변수들은 해당 메소드가 수행되는 동안 필요 하며 메소드수행이 끝나면 필요없게된다

즉 메소드가 호출 될 때 필요로 되는 변수들을 스택에 저장하고 , 메소드 실행이 끝나면 스택을 반환한다. JVM은 이러한 스택 영역을 실행중인 프로그램(쓰레드)에 따라 각각 구성하게된다.

 

LIFO(last-in-first-out)각 메소드를 위한 메모리상의 작업공간을 서로 구별되며(프레임),언제나 호출스택의 제일 위에 있는 메소드가 현재 실행중인 메소드 아래에 있는 메소드가 바로 위의 메소드를 호출한 메소드가 된다.

 

4.Nativa 메소드 스택

자바언어가 아닌 기존의 다른 언어에서 제공되는 메소드를 의미한다

그러 메소드의 매개변수,지역변수등을 저장한다 . 


0 Comments
댓글쓰기 폼