JVM의 메모리 구조와 이를 OS 메모리 상의 어느 영역에 위치시키는가에 대한 궁금증이 들어서 정리했다.
단순히 JVM 내부 메모리 구조만 보는 것이 아니라, OS 프로세스 관점에서 어떤 영역에 어떤 방식으로 매핑되는지까지 확인하고 싶었다.
JVM은 결국 운영체제 위에서 실행되는 하나의 사용자 프로세스다.
따라서 JVM이 사용하는 메모리도 OS가 관리하는 프로세스 메모리 공간 안에서 할당되고 운영된다.
JVM은 내부적으로 다음과 같은 주요 메모리 영역을 가진다.
| JVM 메모리 영역 | 설명 |
|---|---|
| Heap | new로 생성된 객체 저장, GC 대상 영역 |
| Stack | 스레드마다 생성되는 호출 스택, 지역 변수 저장 |
| Metaspace | 클래스 로딩 정보, static 메서드 정의 등 |
| Code Cache | JIT 컴파일된 네이티브 코드 저장 |
| Native Memory | JNI 등 네이티브 코드에서 사용하는 메모리 |
| Direct ByteBuffer | Direct 메모리 (네이티브 I/O용) |
| Constant Pool | 클래스별 상수 풀 (문자열, 메서드 참조 등) |
OS 입장에서 보면, JVM의 각 메모리 영역은 다음과 같이 매핑된다.
| JVM 메모리 구조 | OS 메모리 상 위치 | 시스템 호출 |
|---|---|---|
| Heap | mmap 영역 | mmap() |
| Stack | stack segment 또는 mmap | pthread_create, mmap() |
| Metaspace | mmap 영역 | mmap() |
| Code Cache | mmap 영역 | mmap() |
| Native Memory | mmap 또는 heap segment | mmap(), brk() |
| Constant Pool | Metaspace 또는 Heap | 내부 관리 |
mmap()은 운영체제의 시스템 호출로, 파일이나 메모리 블록을 프로세스의 가상 주소 공간에 매핑하는 함수다.
JVM은 malloc() 대신 mmap()을 통해 대용량 메모리를 확보하고, 그 내부에서 Heap, Metaspace 등을 구성한다.
즉, JVM의 주요 메모리는 전통적인 OS heap segment가 아니라, mmap()을 통해 얻은 별도의 메모리 공간에 올라간다.
아래는 가상 메모리 상에서 JVM이 사용하는 영역들이 실제로 어떻게 배치되는지를 도식화한 것이다.
↓ 낮은 주소
┌────────────────────────────────────────────┐
│ 코드 영역 (Text Segment) │ ← JVM 실행 바이너리 등
├────────────────────────────────────────────┤
│ 데이터 영역 (Data Segment) │ ← 초기화된 static 변수 등
├────────────────────────────────────────────┤
│ 힙 세그먼트 (Heap Segment - brk) │ ← C의 malloc 등
├────────────────────────────────────────────┤
│ mmap 영역 │
│ ├─ JVM Heap │
│ ├─ Metaspace │
│ ├─ CodeCache │
│ ├─ DirectBuffer, Native Memory │
│ └─ Shared Libraries │
├────────────────────────────────────────────┤
│ 스택 영역 (Stack Segment) │ ← 각 스레드별 Stack
└────────────────────────────────────────────┘
↑ 높은 주소
운영체제는 각 프로세스에 대해 고유한 가상 주소 공간을 할당한다.
이 주소는 낮은 주소(0x00000000)에서 시작하여 높은 주소(0xFFFFFFFF...)까지 증가한다.
이 구조 때문에 Heap과 Stack이 충돌하면 StackOverflow, OutOfMemory 같은 문제가 발생할 수 있다.