JVM Structure #1 - Class Loader

mangoo·2022년 11월 10일
0

JVM

목록 보기
1/1

개요

JVM이란?

JVM은 Java Virtual Machine으로, Java 프로그램을 실행할 수 있는 런타임 환경을 제공한다. JVM은 OS나 디바이스에 종속되지 않고 어디서든 Java 프로그램을 실행할 수 있도록 해주고, Java 프로그램의 메모리를 관리해준다.

JVM의 정의는 2가지 측면에서 해석할 수 있다.

  • JVM은 software specification(소프트웨어 명세)이다. 이 spec에는 구현에 대한 자세한 사항이 정의되어 있지 않으며, class 파일을 읽어 spec에 명세된 operation을 정확히 수행하기만 하면 된다.
  • JVM은 spec을 구현(implement)한 실제 소프트웨어 프로그램을 의미한다. JVM spec을 구현한 여러 프로그램이 존재하며, 가장 흔하게 사용되는 JVM으로는 Oracle의 HotSpot이 있다. 대부분의 경우 "JVM"이라고 하면 개발이나 운영 환경에서 실행 중인 소프트웨어 프로그램을 의미한다.

JVM 구조

전체적인 구조는 아래와 같다. 일단 Class Loader SubSystem, Runtime Data Areas, Execution Engine이 있다는 것만 알아두고 Java 프로그램이 실행되는 과정을 살펴보자.

Java 프로그램은 크게 5가지의 과정을 거쳐 실행된다.

  1. JVM은 OS로부터 이 프로그램이 필요로 하는 메모리를 할당 받는다.
  2. 자바 컴파일러(javac.exe)가 자바 소스코드(.java)를 읽어 자바 바이트코드(.class)를 생성한다.
  3. Class Loader를 통해 .class 파일을 JVM에 로드한다.
  4. 로딩된 바이트코드는 Execution Engine을 통해 해석된다.
  5. 해석된 바이트코드는 Runtime Data Areas에 배치되어 실질적인 수행(main 함수 실행)이 이루어지게 된다. 이러한 실행 과정 속에서 JVM은 필요에 따라 Thread Synchronization과 GC 같은 관리 작업을 수행한다.

자바 바이트코드(Java bytecode)는 JVM이 이해할 수 있는 언어로 변환된 자바 소스코드이다. 이 바이트코드는 다시 Interpreter 또는 JIT 컴파일러에 의해 컴퓨터가 인식할 수 있는 바이너리 코드로 변환된다.

이제 Class Loader, Execution Engine, Runtime Data Areas의 역할을 더 자세히 살펴보자.



Class Loader Subsystem

Class Loader는 런타임에 클래스가 처음으로 참조될 때 해당 클래스 파일을 찾은 뒤 Loading, Linking, Initialization 3단계를 거쳐 JVM에 동적으로 로드하는 역할을 한다.

Loading

Class Loader가 클래스 파일을 읽고 이에 대응하는 바이너리 데이터를 생성해 메서드 영역에 저장한다. 각각의 클래스 파일에 대해 JVM은 아래의 정보를 저장한다.

  • FQCN(Fully Qualified Class Name): java.lang.String과 같이 클래스가 속한 패키지명을 모두 포함한 이름이다.
  • Class, Interface, Enum과 같은 속성
  • 생성자, 변수, 메서드

로딩이 끝나면 해당 클래스 타입의 Class 객체를 생성해 힙 영역에 저장한다.

메서드 영역과 힙 영역에 대한 내용은 JVM Structure#2 - Runtime Data Areas 포스팅에서 자세히 설명한다.

Class Loader의 종류

위 과정은 하나의 Class Loader에서 이루어지지 않는다. Bootstrap Class Loader, Extension Class Loader, Application Class Loader가 순차적으로 실행되며 각 Loader가 맡은 클래스 파일들을 로딩한다.

  • Bootstrap Class Loader: 가장 높은 우선순위를 가지며, rt.jar를 포함해 JVM을 구동하기 위한 가장 필수적인 라이브러리 클래스들을 JVM에 로딩하는 역할을 한다. 모든 JVM에 기본적으로 탑재된 Class Loader이며, Java가 아닌 C/C++과 같은 네이티브 언어로 구현되어 있다.
    [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/]
	...중략...

Class Loader의 실행 과정

앞서 Bootstrap Class Loader, Extension Class Loader, Application Class Loader가 순차적으로 실행된다고 언급했는데 이 과정은 아래와 같다.

  • JVM이 특정 클래스를 로딩해야 하는 상황이라면 일단 JVM 메서드 영역을 확인해 로딩 여부를 판단한다. 기존에 로딩된 클래스가 아니라면 JVM은 Class Loader에 로딩을 요청하고, 이 요청은 Application Class Loader에게 먼저 전달된다.
  • Application Class Loader는 요청을 Extension Class Loader로 위임하고, Extension Class Loader는 Bootstrap Class Loader에게 위임한다.
  • Bootstrap Class Loader는 자신이 담당하는 경로의 클래스 파일을 확인하고, 해당하는 클래스가 있으면 로딩한다. 요청에 맞는 클래스를 찾지 못한 경우 Extension Class Loader로 요청을 위임한다.
  • 이 과정을 쭉 거쳐 마지막 Application Class Loader에서도 요청에 맞는 클래스를 찾지 못한다면 ClassNotFoundException을 던진다.

코드로 살펴보자!

  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

Linking은 로드된 클래스 파일들을 검증하고 사용할 수 있도록 준비하는 과정이다. Verification, Preparation, Resolution 세 단계로 이루어진다.

  • Verification: Java Compiler에 의해 생성된 자바 바이트코드(.class)가 유효한지 검증한다. 유효하지 않다면 VerifyError를 던진다.
  • Preparation: 클래스 및 인터페이스에 필요한 static field 메모리를 할당하고, 이를 기본값으로 초기화한다. 기본값으로 초기화된 static field 값들은 뒤의 Initialization 과정에서 코드에 작성한 초기값으로 변경된다.
  • Resolution: 심볼릭 메모리 레퍼런스를 메모리 영역(메서드 영역)에 있는 실제 레퍼런스로 교체한다.

Initialization

Linking 과정을 거치면 Initialization 단계에서는 클래스 파일의 코드를 읽게 된다. Java 코드에 지정된 값들로 초기화하거나 초기화 메서드(static block)를 실행시켜준다. 이때, JVM 은 멀티 쓰레딩으로 작동하기 때문에 초기화 단계에서도 동시성을 고려해주어야 한다. Class Loader 를 통한 클래스 로딩 과정이 끝나면 본격적으로 JVM 에서 클래스 파일을 구동시킬 준비가 끝나게 된다.



Reference

JVM

Class Loader

0개의 댓글