HotSpot JVM
1. 개요
HotSpot JVM은 오라클(Oracle)과 오픈 JDK(OpenJDK) 커뮤니티에서 개발한 자바 가상 머신(Java Virtual Machine)의 구현체로, 실행 중에 프로그램의 성능을 동적으로 분석하여 최적화하는 적응형 최적화 기술을 핵심으로 하는 고성능 런타임 환경이다.
'HotSpot'이라는 명칭은 프로그램 실행 중 빈번하게 호출되어 성능에 결정적인 영향을 미치는 코드 영역, 즉 '핫스팟(Hot Spot)'을 찾아내어 이를 기계어로 컴파일함으로써 실행 속도를 극대화한다는 개념에서 유래하였다. 이는 정적인 컴파일 방식과 달리, 실제 실행 데이터(Profiling Data)를 기반으로 최적화를 수행한다는 점에서 차별화된다.
2. 동작 원리 및 아키텍처
HotSpot JVM은 자바 바이트코드(.class 파일)를 읽어 들여 대상 OS와 CPU 아키텍처에 맞는 기계어로 변환하고 실행하는 복합적인 구조를 가진다.
2.1 주요 구성 요소
- 클래스 로더(Class Loader): 컴파일된 바이트코드를 런타임 데이터 영역으로 로드하며, 로딩 $\rightarrow$ 링크 $\rightarrow$ 초기화 과정을 거친다.
- 런타임 데이터 영역(Runtime Data Areas):
- Heap: 모든 객체와 배열이 저장되는 공유 영역으로, 가비지 컬렉션(GC)의 주 대상이다.
- Stack: 각 스레드별로 생성되며, 메서드 호출 시 생성되는 스택 프레임(지역 변수, 연산 스택 등)을 저장한다.
- Method Area: 클래스 구조, 필드 및 메서드 데이터, 상수 풀(Constant Pool)이 저장되는 공유 영역이다. Java 8 이후부터는 구현체에 따라 Metaspace로 대체되어 네이티브 메모리 영역에서 관리된다.
- PC Register: 현재 실행 중인 JVM 명령의 주소를 저장한다.
graph TD
subgraph "Runtime Data Areas"
subgraph "Shared Areas"
Heap
MethodArea[Method Area / Metaspace]
end
subgraph "Per-Thread Areas"
Stack
PC[PC Register]
end
end
ClassLoader --> MethodArea
ClassLoader --> Heap
ExecutionEngine --> Stack
ExecutionEngine --> PC
ExecutionEngine --> Heap
ExecutionEngine --> MethodArea
- 실행 엔진(Execution Engine): 바이트코드를 실제 기계어로 변환하여 실행한다. 인터프리터와 JIT 컴파일러가 상호 보완적으로 작동한다.
2.2 인터프리터 vs JIT 컴파일러
| 구분 |
인터프리터 (Interpreter) |
JIT 컴파일러 (Just-In-Time Compiler) |
| 작동 방식 |
바이트코드를 한 줄씩 읽어 즉시 실행 |
자주 실행되는 코드 블록을 통째로 기계어로 컴파일 |
| 초기 속도 |
빠름 (컴파일 과정 없이 즉시 실행) |
느림 (컴파일 시간이 소요됨) |
| 실행 속도 |
느림 (매번 해석 과정 필요) |
매우 빠름 (네이티브 코드로 직접 실행) |
| 특징 |
모든 코드를 처리하며 메모리 사용량이 적음 |
'Hot Spot'으로 판명된 코드만 최적화하여 저장 |
3. 적응형 최적화 (Adaptive Optimization)
HotSpot JVM은 프로그램의 실행 패턴을 실시간으로 감시하는 프로파일링(Profiling) 과정을 통해 최적화 대상을 선정한다.
3.1 계층적 컴파일 (Tiered Compilation)
현대적인 HotSpot JVM은 C1(Client Compiler)과 C2(Server Compiler)라는 두 가지 컴파일러를 계층적으로 사용하여 초기 구동 속도와 최대 성능을 동시에 잡는다.
- Level 0 (Interpreted): 모든 코드는 처음에 인터프리터에 의해 실행된다.
- Level 1~3 (C1 Compiler): 코드의 호출 횟수가 임계치를 넘으면 C1 컴파일러가 빠르게 컴파일한다. 이때 기본적인 최적화와 프로파일링 정보 수집이 이루어진다.
- Level 4 (C2 Compiler): 매우 빈번하게 호출되는 'Hot Spot' 코드는 C2 컴파일러가 고도로 최적화된 네이티브 코드로 재컴파일한다. C2는 실행 중인 코드의 패턴을 추측하여 최적화하는 추측성 최적화(Speculative Optimization)를 수행하며, 만약 이 추측이 틀렸을 경우 다시 인터프리터 단계로 되돌리는 탈최적화(Deoptimization) 과정을 거친다.
C2 최적화 예시: 메서드 인라이닝 (Method Inlining)
인라이닝은 메서드 호출 오버헤드를 줄이기 위해 호출되는 메서드의 본문을 호출 지점에 직접 삽입하는 기법이다.
[최적화 전]
int add(int a, int b) {
return a + b;
}
int calculate() {
return add(10, 20); // 메서드 호출 발생
}
[C2 최적화 후 (개념적)]
int calculate() {
return 10 + 20; // add() 메서드의 내용이 직접 삽입되어 호출 오버헤드 제거
}
3.2 JIT 컴파일 흐름도
graph TD
A[Java Source Code] --> B[.class Bytecode]
B --> C{Interpreter}
C --> D{Profiling: Is it Hot?}
D -- No --> C
D -- Yes --> E[C1 Compiler: Quick Optimization]
E --> F{Is it Very Hot?}
F -- No --> G[Execute C1 Native Code]
F -- Yes --> H[C2 Compiler: High Optimization]
H --> I[Execute C2 Native Code]
I --> J[Deoptimization: If assumptions fail]
J --> C
4. 메모리 관리 및 가비지 컬렉션 (GC)
HotSpot JVM은 개발자가 직접 메모리를 해제할 필요 없이, 더 이상 참조되지 않는 객체를 자동으로 제거하는 가비지 컬렉션(GC) 메커니즘을 제공한다.
4.1 힙 메모리 구조 (Generational Heap)
대부분의 객체는 생성 후 금방 소멸한다는 '약한 세대 가설(Weak Generational Hypothesis)'에 기반하여 힙을 나눈다.
* Young Generation: 새롭게 생성된 객체가 할당되는 영역이다. 객체는 먼저 Eden 영역에서 생성되며, Minor GC를 통해 살아남은 객체들이 Survivor 영역으로 이동한다. 이후 여러 번의 GC에서도 살아남은 객체는 최종적으로 Old 영역으로 승격(Promotion)된다.
* Old Generation: Young 영역에서 살아남은 객체들이 이동하는 영역. 크기가 크며 GC 발생 시 중단 시간(Stop-the-world)이 길다.
4.2 주요 GC 알고리즘 비교
| 알고리즘 |
특징 |
적합한 사용 사례 |
비고 |
| G1 GC |
힙을 Region으로 나누어 효율적으로 관리 |
대용량 힙, 예측 가능한 일시 정지 시간 필요 시 |
Java 9+ 기본 GC |
| ZGC |
컬러 포인터를 사용하여 매우 낮은 지연 시간 구현 |
테라바이트 단위의 초거대 힙, Low Latency 필수 서비스 |
Concurrent GC |
| Shenandoah |
동시 압착(Concurrent Compaction) 지원 |
STW 시간을 최소화해야 하는 실시간 응답 시스템 |
Red Hat 주도 개발 |
| Parallel GC |
여러 스레드로 GC 수행, 처리량(Throughput) 극대화 |
백그라운드 배치 작업, 응답 시간보다 처리량이 중요할 때 |
Java 8 기본 GC |
JVM 옵션을 통해 애플리케이션의 특성에 맞게 메모리 크기와 GC 동작 방식을 제어할 수 있다.
5.1 주요 JVM 플래그
-Xms<size>: JVM 힙 메모리의 초기 크기 설정
-Xmx<size>: JVM 힙 메모리의 최대 크기 설정
-XX:+UseG1GC: G1 가비지 컬렉터 사용 설정
-XX:MaxGCPauseMillis<ms>: 최대 GC 일시 정지 목표 시간 설정
-Xlog:gc: GC 로그 출력. Java 8까지는 -XX:+PrintGCDetails를 사용했으나, Java 9 이후부터는 Unified Logging 방식인 -Xlog:gc 옵션으로 통합되었다.
5.2 실행 옵션 설정 예시
# 힙 메모리를 2GB로 고정하고 G1 GC를 사용하며, 최대 정지 시간을 200ms로 제한하는 설정
java -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar my-application.jar
5.3 GC 튜닝 시 주의사항 및 체크리스트
주의사항:
* 과도한 튜닝 금지: 최신 JVM(Java 11, 17+)은 기본 설정이 매우 우수하므로, 명확한 지표 없이 옵션을 추가하는 것은 오히려 성능을 저하시킬 수 있다.
* Xms와 Xmx의 일치: 서버 환경에서는 힙 크기 확장 시 발생하는 오버헤드를 줄이기 위해 두 값을 동일하게 설정하는 것이 권장된다.
체크리스트:
- [ ] 애플리케이션의 최대 메모리 사용량(Working Set)을 파악했는가?
- [ ] 서비스의 핵심 지표가 '처리량(Throughput)'인가, '응답 시간(Latency)'인가?
- [ ] GC 로그를 통해 Stop-the-world 시간이 허용 범위 내에 있는지 확인했는가?
- [ ] 메모리 누수(Memory Leak)가 없는지 힙 덤프(Heap Dump) 분석을 수행했는가?
6. JVM 버전별 주요 변경 사항
| 버전 |
주요 변경 사항 |
영향 |
| Java 8 |
PermGen 영역 제거 $\rightarrow$ Metaspace 도입 |
클래스 메타데이터가 Native Memory로 이동하여 OutOfMemoryError 감소 |
| Java 9 |
G1 GC 기본값 채택, 모듈 시스템(Jigsaw) 도입 |
힙 관리 효율성 증대 및 런타임 경량화 기반 마련 |
| Java 11 |
ZGC(Experimental) 도입, Flight Recorder 오픈소스화 |
초저지연 GC 가능성 제시 및 성능 분석 도구 대중화 |
| Java 17 |
ZGC 정식 도입, 강력한 캡슐화(Strong Encapsulation) |
대규모 힙 환경의 안정성 확보 및 보안 강화 |
| Java 21 |
Virtual Threads (Project Loom) 도입 |
OS 스레드 의존도를 낮춰 동시성 처리 성능 획기적 향상 |
7. 관련 기술 및 생태계
- OpenJDK: HotSpot JVM의 오픈 소스 구현체로, 대부분의 상용 JVM(Oracle JDK, Amazon Corretto, Azul Zulu 등)의 기반이 된다.
- GraalVM: HotSpot을 확장한 고성능 JVM이다. HotSpot JVM의 JIT 컴파일러 자체를 Graal 컴파일러로 교체하여 더 높은 최적화 성능을 낼 수 있으며, AOT(Ahead-of-Time) 컴파일을 통한 네이티브 이미지(Native Image) 생성으로 시작 시간과 메모리 사용량을 획기적으로 줄일 수 있다.
- 다국어 지원: HotSpot JVM은 자바뿐만 아니라 바이트코드로 컴파일되는 Kotlin, Scala, Groovy, Clojure 등 다양한 JVM 언어를 동일한 최적화 메커니즘으로 실행한다.
# HotSpot JVM
## 1. 개요
**HotSpot JVM**은 오라클(Oracle)과 오픈 JDK(OpenJDK) 커뮤니티에서 개발한 자바 가상 머신(Java Virtual Machine)의 구현체로, 실행 중에 프로그램의 성능을 동적으로 분석하여 최적화하는 적응형 최적화 기술을 핵심으로 하는 고성능 런타임 환경이다.
'HotSpot'이라는 명칭은 프로그램 실행 중 빈번하게 호출되어 성능에 결정적인 영향을 미치는 코드 영역, 즉 **'핫스팟(Hot Spot)'**을 찾아내어 이를 기계어로 컴파일함으로써 실행 속도를 극대화한다는 개념에서 유래하였다. 이는 정적인 컴파일 방식과 달리, 실제 실행 데이터(Profiling Data)를 기반으로 최적화를 수행한다는 점에서 차별화된다.
---
## 2. 동작 원리 및 아키텍처
HotSpot JVM은 자바 바이트코드(`.class` 파일)를 읽어 들여 대상 OS와 CPU 아키텍처에 맞는 기계어로 변환하고 실행하는 복합적인 구조를 가진다.
### 2.1 주요 구성 요소
* **클래스 로더(Class Loader):** 컴파일된 바이트코드를 런타임 데이터 영역으로 로드하며, 로딩 $\rightarrow$ 링크 $\rightarrow$ 초기화 과정을 거친다.
* **런타임 데이터 영역(Runtime Data Areas):**
* **Heap:** 모든 객체와 배열이 저장되는 공유 영역으로, 가비지 컬렉션(GC)의 주 대상이다.
* **Stack:** 각 스레드별로 생성되며, 메서드 호출 시 생성되는 스택 프레임(지역 변수, 연산 스택 등)을 저장한다.
* **Method Area:** 클래스 구조, 필드 및 메서드 데이터, 상수 풀(Constant Pool)이 저장되는 공유 영역이다. Java 8 이후부터는 구현체에 따라 **Metaspace**로 대체되어 네이티브 메모리 영역에서 관리된다.
* **PC Register:** 현재 실행 중인 JVM 명령의 주소를 저장한다.
```mermaid
graph TD
subgraph "Runtime Data Areas"
subgraph "Shared Areas"
Heap
MethodArea[Method Area / Metaspace]
end
subgraph "Per-Thread Areas"
Stack
PC[PC Register]
end
end
ClassLoader --> MethodArea
ClassLoader --> Heap
ExecutionEngine --> Stack
ExecutionEngine --> PC
ExecutionEngine --> Heap
ExecutionEngine --> MethodArea
```
* **실행 엔진(Execution Engine):** 바이트코드를 실제 기계어로 변환하여 실행한다. 인터프리터와 JIT 컴파일러가 상호 보완적으로 작동한다.
### 2.2 인터프리터 vs JIT 컴파일러
| 구분 | 인터프리터 (Interpreter) | JIT 컴파일러 (Just-In-Time Compiler) |
| :--- | :--- | :--- |
| **작동 방식** | 바이트코드를 한 줄씩 읽어 즉시 실행 | 자주 실행되는 코드 블록을 통째로 기계어로 컴파일 |
| **초기 속도** | 빠름 (컴파일 과정 없이 즉시 실행) | 느림 (컴파일 시간이 소요됨) |
| **실행 속도** | 느림 (매번 해석 과정 필요) | 매우 빠름 (네이티브 코드로 직접 실행) |
| **특징** | 모든 코드를 처리하며 메모리 사용량이 적음 | 'Hot Spot'으로 판명된 코드만 최적화하여 저장 |
---
## 3. 적응형 최적화 (Adaptive Optimization)
HotSpot JVM은 프로그램의 실행 패턴을 실시간으로 감시하는 **프로파일링(Profiling)** 과정을 통해 최적화 대상을 선정한다.
### 3.1 계층적 컴파일 (Tiered Compilation)
현대적인 HotSpot JVM은 C1(Client Compiler)과 C2(Server Compiler)라는 두 가지 컴파일러를 계층적으로 사용하여 초기 구동 속도와 최대 성능을 동시에 잡는다.
1. **Level 0 (Interpreted):** 모든 코드는 처음에 인터프리터에 의해 실행된다.
2. **Level 1~3 (C1 Compiler):** 코드의 호출 횟수가 임계치를 넘으면 C1 컴파일러가 빠르게 컴파일한다. 이때 기본적인 최적화와 프로파일링 정보 수집이 이루어진다.
3. **Level 4 (C2 Compiler):** 매우 빈번하게 호출되는 'Hot Spot' 코드는 C2 컴파일러가 고도로 최적화된 네이티브 코드로 재컴파일한다. C2는 실행 중인 코드의 패턴을 추측하여 최적화하는 **추측성 최적화(Speculative Optimization)**를 수행하며, 만약 이 추측이 틀렸을 경우 다시 인터프리터 단계로 되돌리는 **탈최적화(Deoptimization)** 과정을 거친다.
#### C2 최적화 예시: 메서드 인라이닝 (Method Inlining)
인라이닝은 메서드 호출 오버헤드를 줄이기 위해 호출되는 메서드의 본문을 호출 지점에 직접 삽입하는 기법이다.
**[최적화 전]**
```java
int add(int a, int b) {
return a + b;
}
int calculate() {
return add(10, 20); // 메서드 호출 발생
}
```
**[C2 최적화 후 (개념적)]**
```java
int calculate() {
return 10 + 20; // add() 메서드의 내용이 직접 삽입되어 호출 오버헤드 제거
}
```
### 3.2 JIT 컴파일 흐름도
```mermaid
graph TD
A[Java Source Code] --> B[.class Bytecode]
B --> C{Interpreter}
C --> D{Profiling: Is it Hot?}
D -- No --> C
D -- Yes --> E[C1 Compiler: Quick Optimization]
E --> F{Is it Very Hot?}
F -- No --> G[Execute C1 Native Code]
F -- Yes --> H[C2 Compiler: High Optimization]
H --> I[Execute C2 Native Code]
I --> J[Deoptimization: If assumptions fail]
J --> C
```
---
## 4. 메모리 관리 및 가비지 컬렉션 (GC)
HotSpot JVM은 개발자가 직접 메모리를 해제할 필요 없이, 더 이상 참조되지 않는 객체를 자동으로 제거하는 가비지 컬렉션(GC) 메커니즘을 제공한다.
### 4.1 힙 메모리 구조 (Generational Heap)
대부분의 객체는 생성 후 금방 소멸한다는 **'약한 세대 가설(Weak Generational Hypothesis)'**에 기반하여 힙을 나눈다.
* **Young Generation:** 새롭게 생성된 객체가 할당되는 영역이다. 객체는 먼저 Eden 영역에서 생성되며, Minor GC를 통해 살아남은 객체들이 Survivor 영역으로 이동한다. 이후 여러 번의 GC에서도 살아남은 객체는 최종적으로 Old 영역으로 **승격(Promotion)**된다.
* **Old Generation:** Young 영역에서 살아남은 객체들이 이동하는 영역. 크기가 크며 GC 발생 시 중단 시간(Stop-the-world)이 길다.
### 4.2 주요 GC 알고리즘 비교
| 알고리즘 | 특징 | 적합한 사용 사례 | 비고 |
| :--- | :--- | :--- | :--- |
| **G1 GC** | 힙을 Region으로 나누어 효율적으로 관리 | 대용량 힙, 예측 가능한 일시 정지 시간 필요 시 | Java 9+ 기본 GC |
| **ZGC** | 컬러 포인터를 사용하여 매우 낮은 지연 시간 구현 | 테라바이트 단위의 초거대 힙, Low Latency 필수 서비스 | Concurrent GC |
| **Shenandoah** | 동시 압착(Concurrent Compaction) 지원 | STW 시간을 최소화해야 하는 실시간 응답 시스템 | Red Hat 주도 개발 |
| **Parallel GC** | 여러 스레드로 GC 수행, 처리량(Throughput) 극대화 | 백그라운드 배치 작업, 응답 시간보다 처리량이 중요할 때 | Java 8 기본 GC |
---
## 5. 성능 튜닝 및 JVM 옵션
JVM 옵션을 통해 애플리케이션의 특성에 맞게 메모리 크기와 GC 동작 방식을 제어할 수 있다.
### 5.1 주요 JVM 플래그
* `-Xms<size>`: JVM 힙 메모리의 초기 크기 설정
* `-Xmx<size>`: JVM 힙 메모리의 최대 크기 설정
* `-XX:+UseG1GC`: G1 가비지 컬렉터 사용 설정
* `-XX:MaxGCPauseMillis<ms>`: 최대 GC 일시 정지 목표 시간 설정
* `-Xlog:gc`: GC 로그 출력. Java 8까지는 `-XX:+PrintGCDetails`를 사용했으나, Java 9 이후부터는 Unified Logging 방식인 `-Xlog:gc` 옵션으로 통합되었다.
### 5.2 실행 옵션 설정 예시
```bash
# 힙 메모리를 2GB로 고정하고 G1 GC를 사용하며, 최대 정지 시간을 200ms로 제한하는 설정
java -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar my-application.jar
```
### 5.3 GC 튜닝 시 주의사항 및 체크리스트
**주의사항:**
* **과도한 튜닝 금지:** 최신 JVM(Java 11, 17+)은 기본 설정이 매우 우수하므로, 명확한 지표 없이 옵션을 추가하는 것은 오히려 성능을 저하시킬 수 있다.
* **Xms와 Xmx의 일치:** 서버 환경에서는 힙 크기 확장 시 발생하는 오버헤드를 줄이기 위해 두 값을 동일하게 설정하는 것이 권장된다.
**체크리스트:**
- [ ] 애플리케이션의 최대 메모리 사용량(Working Set)을 파악했는가?
- [ ] 서비스의 핵심 지표가 '처리량(Throughput)'인가, '응답 시간(Latency)'인가?
- [ ] GC 로그를 통해 Stop-the-world 시간이 허용 범위 내에 있는지 확인했는가?
- [ ] 메모리 누수(Memory Leak)가 없는지 힙 덤프(Heap Dump) 분석을 수행했는가?
---
## 6. JVM 버전별 주요 변경 사항
| 버전 | 주요 변경 사항 | 영향 |
| :--- | :--- | :--- |
| **Java 8** | PermGen 영역 제거 $\rightarrow$ Metaspace 도입 | 클래스 메타데이터가 Native Memory로 이동하여 `OutOfMemoryError` 감소 |
| **Java 9** | G1 GC 기본값 채택, 모듈 시스템(Jigsaw) 도입 | 힙 관리 효율성 증대 및 런타임 경량화 기반 마련 |
| **Java 11** | ZGC(Experimental) 도입, Flight Recorder 오픈소스화 | 초저지연 GC 가능성 제시 및 성능 분석 도구 대중화 |
| **Java 17** | ZGC 정식 도입, 강력한 캡슐화(Strong Encapsulation) | 대규모 힙 환경의 안정성 확보 및 보안 강화 |
| **Java 21** | Virtual Threads (Project Loom) 도입 | OS 스레드 의존도를 낮춰 동시성 처리 성능 획기적 향상 |
---
## 7. 관련 기술 및 생태계
* **OpenJDK:** HotSpot JVM의 오픈 소스 구현체로, 대부분의 상용 JVM(Oracle JDK, Amazon Corretto, Azul Zulu 등)의 기반이 된다.
* **GraalVM:** HotSpot을 확장한 고성능 JVM이다. HotSpot JVM의 JIT 컴파일러 자체를 Graal 컴파일러로 교체하여 더 높은 최적화 성능을 낼 수 있으며, **AOT(Ahead-of-Time) 컴파일**을 통한 네이티브 이미지(Native Image) 생성으로 시작 시간과 메모리 사용량을 획기적으로 줄일 수 있다.
* **다국어 지원:** HotSpot JVM은 자바뿐만 아니라 바이트코드로 컴파일되는 **Kotlin, Scala, Groovy, Clojure** 등 다양한 JVM 언어를 동일한 최적화 메커니즘으로 실행한다.