프로그래밍 언어/JAVA

[정보] JVM 아키텍처

투칼론 2023. 3. 12. 22:36
반응형

(원문) https://dzone.com/articles/jvm-architecture-explained

 

모든 Java 개발자는 바이트코드가 JRE(Java Runtime Environment)에 의해 실행된다는 것을 알고 있습니다. 그러나 많은 사람들이 JRE가 바이트코드를 분석하고 코드를 해석하고 실행하는 JVM(Java Virtual Machine)의 구현이라는 사실을 모르고 있습니다. 코드를 보다 효율적으로 작성할 수 있게 해주는 JVM의 아키텍처를 아는 것은 개발자로서 매우 중요합니다. 이 기사에서는 Java의 JVM 아키텍처와 JVM의 다양한 구성 요소에 대해 자세히 알아볼 것입니다.

 

1. JVM 이란?

VM은 물리적 머신의 소프트웨어 구현입니다. Java는 VM에서 실행되는 WORA(Write Once Run Anywhere) 개념으로 개발되었습니다. 컴파일러는 Java 파일을 Java .class 파일로 컴파일하고, 해당 .class 파일을 로드하고 실행합니다. 아래는 JVM 아키텍처의 다이어그램입니다.

 

2. JVM 아키텍처 다이어그램

3. JVM 작동 방식

위의 아키텍처 다이어그램에 표시된 것처럼 JVM은 세 가지 주요 하위 시스템으로 나뉩니다.

- 클래스로더(ClassLoader) 서브시스템
- 런타임(Runtime) 데이터 영역
- 실행 엔진(Execution Engine)

 

3.1 클래스로더 서브시스템
Java의 동적 클래스 로딩 기능은 ClassLoader 하위 시스템에서 처리합니다. 로딩와 링크하고, 컴파일 시간이 아니라 런타임에 처음으로 클래스를 참조할 때 클래스 파일을 초기화합니다.

 

3.1.1 로딩(Loading)
클래스는 이 구성 요소에 의해 로드됩니다. BootStrap ClassLoader, Extension ClassLoader 및 Application ClassLoader는 이를 달성하는 데 도움이 되는 세 가지 ClassLoader입니다.

- BootStrap ClassLoader : 부트스트랩 클래스 경로에서 rt.jar만 로드하는 클래스를 담당합니다. 이 로더에 가장 높은 우선 순위가 부여됩니다.
- Extension ClassLoader : ext 폴더(jre\lib) 내부에 있는 클래스 로드를 담당합니다.
- Application ClassLoader : Application Level Classpath, 경로 언급 환경 변수 등을 로드하는 역할을 합니다.

 

위의 ClassLoader는 클래스 파일을 로드하는 동안 Delegation Hierarchy Algorithm을 따릅니다.

 

3.1.2 연결(Linking)
- Verify : 바이트코드 Verifier는 생성된 바이트코드가 적절한지 여부를 verify합니다. verification에 실패하면 verification 오류가 발생합니다.
- Prepare : 모든 정적 변수에 대해 메모리가 할당되고 기본값으로 할당됩니다.
- Resolve : 모든 기호 메모리 참조는 메서드 영역의 원래 참조로 대체됩니다.

 

3.1.3 초기화(Initialization)
이것이 ClassLoading의 마지막 단계입니다. 여기서 모든 정적 변수(static variables)는 원래 값으로 할당되고 정적 블록(static block)이 실행됩니다.

 

3.2 런타임 데이터 영역
런타임 데이터 영역은 5개의 주요 구성 요소로 나뉩니다.

1) 메서드 영역 : 정적 변수를 포함하여 모든 클래스 수준 데이터가 여기에 저장됩니다. JVM당 하나의 메소드 영역만 있으며 공유 자원입니다.
2) 힙 영역 : 모든 개체와 해당 인스턴스 변수 및 배열이 여기에 저장됩니다. JVM당 하나의 힙 영역도 있습니다. 메서드 및 힙 영역은 여러 스레드에 대한 메모리를 공유하므로 저장된 데이터는 스레드로부터 안전하지 않습니다.
3) 스택 영역 – 모든 스레드에 대해 별도의 런타임 스택이 생성됩니다. 모든 메서드 호출에 대해 스택 프레임이라는 스택 메모리에 하나의 항목이 만들어집니다. 모든 로컬 변수는 스택 메모리에 생성됩니다. 스택 영역은 공유 리소스가 아니므로 스레드로부터 안전합니다. 스택 프레임은 세 가지 하위 항목으로 나뉩니다.
    - Local Variable Array : 관련된 지역 변수의 수와 해당 값이 여기에 저장되는 방법과 관련됩니다.
    - Operand Stack : 수행하는 데 중간 작업이 필요한 경우 Operand Stack은 작업을 수행하기 위한 런타임 작업 공간 역할을 합니다.
    - Frame Data : Method에 해당하는 모든 기호가 여기에 저장됩니다. 예외가 발생한 경우 catch 블록 정보는 Frame Data에 유지됩니다.
4) PC 레지스터 : 각 스레드에는 별도의 PC 레지스터가 있으며, 명령이 실행되면 현재 실행 중인 명령의 주소를 보유하고 PC 레지스터는 다음 명령으로 업데이트됩니다.
5) 네이티브 메서드 스택 : 네이티브 메서드 스택은 네이티브 메서드 정보를 보유합니다. 모든 스레드에 대해 별도의 기본 메서드 스택이 생성됩니다.

 

3.3 실행 엔진
런타임 데이터 영역에 할당된 바이트코드는 실행 엔진에 의해 실행됩니다. 실행 엔진은 바이트코드를 읽고 하나씩 실행합니다.

1) 인터프리터 : 인터프리터는 바이트코드를 더 빠르게 해석하지만 실행 속도가 느립니다. 인터프리터의 단점은 하나의 메서드를 여러 번 호출할 때마다 새로운 해석이 필요하다는 점입니다.
2) JIT 컴파일러 : JIT 컴파일러는 인터프리터의 단점을 보완합니다. 실행 엔진은 바이트 코드를 변환할 때 인터프리터의 도움을 받지만 반복되는 코드를 발견하면 전체 바이트 코드를 컴파일하고, 네이티브 코드로 변경하는 JIT 컴파일러를 사용합니다. 이 네이티브 코드는 반복되는 메서드 호출에 직접 사용되어 시스템 성능을 향상시킵니다.
     - Interpreter Code Generator : 중간 코드를 생성
     - Code Optimizer : 위에서 생성된 중간 코드를 최적화하는 역할
     - Target Code Generator : 머신 코드 또는 네이티브 코드 생성 담당
     - Profiler : hotspots 찾기를 담당하는 특수 구성 요소, 즉 메서드가 여러 번 호출되는지 여부.
3) Garbate Collector : 참조되지 않은 개체를 수집하고 제거합니다. Garbage Collection은 System.gc()를 호출하여 트리거할 수 있지만 실행이 보장되지는 않습니다. JVM의 가비지 수집은 생성된 객체를 수집합니다.

 

JNI(Java Native Interface) : JNI는 기본 메서드 라이브러리와 상호 작용하고 실행 엔진에 필요한 기본 라이브러리를 제공합니다.

네이티브 메서드 라이브러리: 실행 엔진에 필요한 네이티브 라이브러리 모음입니다.