JVM은 Java Virtual Machine으로, Java 프로그램을 실행할 수 있는 런타임 환경을 제공한다. JVM은 OS나 디바이스에 종속되지 않고 어디서든 Java 프로그램을 실행할 수 있도록 해주고, Java 프로그램의 메모리를 관리해준다.
JVM의 정의는 2가지 측면에서 해석할 수 있다.
전체적인 구조는 아래와 같다. 일단 Class Loader SubSystem, Runtime Data Areas, Execution Engine이 있다는 것만 알아두고 Java 프로그램이 실행되는 과정을 살펴보자.
Java 프로그램은 크게 5가지의 과정을 거쳐 실행된다.
자바 바이트코드(Java bytecode)는 JVM이 이해할 수 있는 언어로 변환된 자바 소스코드이다. 이 바이트코드는 다시 Interpreter 또는 JIT 컴파일러에 의해 컴퓨터가 인식할 수 있는 바이너리 코드로 변환된다.
이제 Class Loader, Execution Engine, Runtime Data Areas의 역할을 더 자세히 살펴보자.
Class Loader는 런타임에 클래스가 처음으로 참조될 때 해당 클래스 파일을 찾은 뒤 Loading, Linking, Initialization 3단계를 거쳐 JVM에 동적으로 로드하는 역할을 한다.
Class Loader가 클래스 파일을 읽고 이에 대응하는 바이너리 데이터를 생성해 메서드 영역에 저장한다. 각각의 클래스 파일에 대해 JVM은 아래의 정보를 저장한다.
로딩이 끝나면 해당 클래스 타입의 Class 객체를 생성해 힙 영역에 저장한다.
메서드 영역과 힙 영역에 대한 내용은 JVM Structure#2 - Runtime Data Areas 포스팅에서 자세히 설명한다.
위 과정은 하나의 Class Loader에서 이루어지지 않는다. Bootstrap Class Loader, Extension Class Loader, Application Class Loader가 순차적으로 실행되며 각 Loader가 맡은 클래스 파일들을 로딩한다.
[Opened /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar]
[Loaded java.lang.Object from /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar]
[Loaded java.io.Serializable from /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar]
...중략...
Extension Class Loader: Bootstrap Class Loader의 자식 클래스이고, 두 번째로 높은 우선순위를 가진다. localedata, zipfs 등 다른 표준 핵심 라이브러리 클래스들을 JVM에 로딩하는 역할을 한다. Extension Class Loader는 Java로 구현되었으며, 이에 대응하는 클래스 파일은 sun.misc.Launcher$ExtClassLoader.class이다.
Application Class Loader: Extension Class Loader의 자식 클래스이고, 세 번째로 높은 우선순위를 가진다. 개발자가 Java로 작성한 클래스 파일을 포함한 애플리케이션 class path에 있는 클래스들을 로딩한다. Application Class Loader/System Class Loader는 Java로 구현되었으며, 이에 대응하는 클래스 파일은 sun.misc.Launcher$AppClassLoader.class이다.
...중략...
[Loaded UserClass from file:/home/hybeom/OTC/java8/src/]
...중략...
앞서 Bootstrap Class Loader, Extension Class Loader, Application Class Loader가 순차적으로 실행된다고 언급했는데 이 과정은 아래와 같다.
public static void main(String[] args) {
System.out.println("Application ClassLoader: " + UserClass.class.getClassLoader());
System.out.println("Extension ClassLoader: " + UserClass.class.getClassLoader().getParent());
System.out.println(
"Bootstrap ClassLoader: " + UserClass.class.getClassLoader().getParent().getParent());
}
static class UserClass {}
getClassLoader() 메서드를 사용하면 해당 클래스를 로드한 Class Loader를 확인할 수 있다. getParent() 메서드는 부모 Class Loader 클래스를 찾아준다. 실행시킨 결과는 아래와 같다.
Application ClassLoader: sun.misc.Launcher$AppClassLoader@75b84c92
Extension ClassLoader: sun.misc.Launcher$ExtClassLoader@4aa298b7
Bootstrap ClassLoader: null
Bootstrap Class Loader는 네이티브 코드로 작성되었기 때문에 null로 표현된다.
Linking은 로드된 클래스 파일들을 검증하고 사용할 수 있도록 준비하는 과정이다. Verification, Preparation, Resolution 세 단계로 이루어진다.
Linking 과정을 거치면 Initialization 단계에서는 클래스 파일의 코드를 읽게 된다. Java 코드에 지정된 값들로 초기화하거나 초기화 메서드(static block)를 실행시켜준다. 이때, JVM 은 멀티 쓰레딩으로 작동하기 때문에 초기화 단계에서도 동시성을 고려해주어야 한다. Class Loader 를 통한 클래스 로딩 과정이 끝나면 본격적으로 JVM 에서 클래스 파일을 구동시킬 준비가 끝나게 된다.