JVM 내부 구조와 동작 원리 - Class Loader, Execution Engine
JVM은 왜 필요한가?
1. 플랫폼 독립성
기존의 C와 같은 언어는 운영체제 별로 개발자가 코드를 달리 작성해야 했는데, 자바 프로그램은 한 번 작성하면, JVM이 설치된 모든 플랫폼에서 실행될 수 있다. 이는 "Write Once, Run Anywhere"라는 자바의 슬로건을 가능하게 한다
2. 메모리 관리
JVM은 자동으로 메모리를 관리한다. 가비지 컬렉션(Garbage Collection)을 통해 더 이상 사용되지 않는 객체를 자동으로 메모리에서 해제하여 메모리 누수를 방지한다.
3. 성능 최적화
JIT(Just-In-Time) 컴파일러를 통해 자주 사용되는 바이트코드를 기계어로 변환하여 실행 성능을 최적화한다.
4. 멀티스레딩 지원
JVM은 멀티스레딩을 기본적으로 지원하여, 병렬 처리를 쉽게 구현할 수 있다.
JVM의 구조
1.클래스 로더 시스템(Class Loader Subsystem)
자바 클래스 파일을 로드하고, 이를 JVM 내에서 사용할 수 있도록 준비한다.
로드, 링크, 초기화 단계를 거친다.
2.런타임 데이터 영역(Runtime Data Area)
JVM이 실행 중에 사용하는 메모리 영역이다.
주요 구성 요소
- 메소드 영역(Method Area): 클래스 수준의 정보(클래스, 메소드, 필드, 상수 풀 등)를 저장
- 힙(Heap): 객체와 배열을 저장하는 영역
- 스택(Stack): 각 스레드마다 생성되며, 메소드 호출 시 생성되는 프레임을 저장.
- PC 레지스터(Program Counter Register): 현재 실행 중인 JVM 명령의 주소를 저장
- 네이티브 메소드 스택(Native Method Stack): 네이티브 메소드 호출 시 사용되는 스택
3.실행 엔진(Execution Engine)
바이트코드를 실제 기계어로 변환하여 실행한다.
주요 구성 요소
- 인터프리터(Interpreter): 바이트코드를 한 줄씩 읽어 해석하고 실행
- JIT 컴파일러(Just-In-Time Compiler): 자주 사용되는 바이트코드를 기계어로 컴파일하여 성능을 향상
- 가비지 컬렉터(Garbage Collector): 사용되지 않는 객체를 메모리에서 해제
5.네이티브 메소드 인터페이스(Native Method Interface, JNI)
자바 코드가 네이티브 코드(C, C++ 등)와 상호작용할 수 있도록 한다.
6.네이티브 메소드 라이브러리(Native Method Libraries):
JNI를 통해 호출되는 네이티브 메소드들을 포함하는 라이브러리
Class Loader
클래스 로더(Class Loader)는 JVM의 구성 요소 중 하나로, 컴파일러에 의해 생성된 자바 클래스(.class) 파일을 메모리에 로드하고 이를 JVM 내에서 사용할 수 있도록 준비하는 역할을 한다.
클래스 로더는 다음과 같은 주요 기능을 수행한다.
1. 로딩(Loading): 클래스 파일을 찾아서 읽어들이고, 이를 메모리에 로드한다.
2. 링크(Linking): 로드된 클래스 파일을 검증하고, 필요한 자원을 준비하며, 심볼릭 레퍼런스를 실제 메모리 주소로 변환한다. 즉, 클래스, 메소드, 필드등을 실제 메모리 주소에 할당하는 과정이다.
- 검증(Verification): 클래스 파일의 형식이 유효한지 검증한다.
- 준비(Preparation): 클래스 변수(static 변수)를 위한 메모리를 할당하고 기본값으로 초기화한다.
- 해석(Resolution): 심볼릭 레퍼런스를 실제 메모리 주소로 변환한다.
3. 초기화(Initialization): 클래스 변수에 사용자 정의 초기값을 할당하고, 클래스 초기화 블록(static 블록)을 실행한다.
클래스 로더는 계층 구조로 구성되어 있으며, 주요 클래스 로더는 다음과 같다.
부트스트랩 클래스 로더(Bootstrap Class Loader): JVM의 핵심 클래스를 로드한다. (예: `java.lang.*`, `java.util.*` 등)
확장 클래스 로더(Extension Class Loader): 확장 클래스 경로에 있는 클래스를 로드한다. (예: `javax.*` 등)
애플리케이션 클래스 로더(Application Class Loader): 애플리케이션 클래스 경로에 있는 클래스를 로드한다. (예: 사용자 정의 클래스)
클래스 로더는 부모-자식 관계로 구성되어 있으며, 클래스 로딩 요청이 발생하면 먼저 부모 클래스 로더에게 위임하는 방식으로 동작한다. 이를 위임 모델(Delegation Model)이라고 한다.
Execution Engine
실행 엔진(Execution Engine)은 JVM의 핵심 구성 요소 중 하나로, 자바 바이트코드를 실제 기계어로 변환하여 실행하는 역할을 한다. 실행 엔진의 주요 구성 요소는 다음과 같다.
1. 인터프리터(Interpreter)
- 바이트코드를 한 줄씩 읽어 해석하고 실행한다.
- 초기 실행 속도가 빠르지만, 반복되는 코드의 실행 속도가 느리다.
2. JIT 컴파일러(Just-In-Time Compiler)
JIT(Just-In-Time) 컴파일러는 JVM의 실행 엔진의 일부로, 자바 바이트코드를 런타임 시점에 기계어로 컴파일하여 프로그램의 실행 성능을 향상시키는 역할을 한다. JIT 컴파일러는 다음과 같은 주요 특징과 기능을 가지고 있다.
동적 컴파일
자주 실행되는 바이트코드를 기계어로 변환하여 캐싱한다. 초기 실행 시에는 인터프리터가 바이트코드를 해석하지만, 특정 코드가 반복적으로 실행되면 JIT 컴파일러가 해당 코드를 기계어로 변환한다.
성능 최적화
- 인라이닝(Inlining): 메소드 호출을 직접 호출하는 코드로 대체하여 호출 오버헤드를 줄인다.
- 루프 최적화(Loop Optimization): 루프 내의 코드를 최적화하여 반복 실행 성능을 향상시킨다.
- 동적 프로파일링(Dynamic Profiling): 실행 중인 프로그램의 프로파일링 정보를 수집하여 최적화에 활용한다.
혼합 모드 실행:
- 인터프리터와 JIT 컴파일러가 함께 동작하여, 초기 실행 속도와 반복 실행 성능을 모두 향상시킨다.
- 인터프리터는 빠른 초기 실행을 제공하고, JIT 컴파일러는 반복 실행 시 성능을 최적화한다.
가비지 컬렉션과의 통합:
JIT 컴파일러는 가비지 컬렉션과 통합되어, 메모리 관리와 성능 최적화를 함께 수행한다.
JIT 컴파일러는 JVM의 성능을 크게 향상시키는 중요한 구성 요소로, 자바 애플리케이션의 실행 속도를 최적화하는 데 중요한 역할을 한다.
3. 가비지 컬렉터(Garbage Collector):
- 사용되지 않는 객체를 메모리에서 해제하여 메모리 누수를 방지한다.
- 다양한 가비지 컬렉션 알고리즘이 있으며, 대표적으로 마크-스위프(Mark-Sweep), 마크-컴팩트(Mark-Compact), 그리고 G1(Garbage-First) 등이 있다.
가비지 컬렉터의 내용 또한 워낙 방대하기 때문에, 자세한 내용은 나중에 알아보는 것이 좋다. 일단은 사용되지 않는 객체를 메모리에서 해제하여 메모리 누수를 방지한다. 라는 특성만 알고가자