JVM 런타임 데이터 영역 정리

JVM 런타임 데이터 영역 정리

JVM이 자바 프로그램 실행 중 메모리를 어떻게 나누어 관리하는지 정리한다.


개요

JVM은 프로그램 실행에 필요한 메모리를 여러 영역으로 나누어 관리한다. 각 영역은 고유한 목적과 생명주기를 가진다.

┌─────────────────────────────────────────────────────────────┐
│                  JVM 런타임 데이터 영역                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   [스레드 공유 영역]                                         │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  힙 (Heap)              │  메서드 영역 (Method Area) │   │
│   │  - 객체 인스턴스 저장    │  - 클래스 메타데이터       │   │
│   │  - GC 대상              │  - 상수, 정적 변수         │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   [스레드 프라이빗 영역]                                      │
│   ┌─────────────┬─────────────┬─────────────────────────┐   │
│   │ PC Register │  JVM Stack  │  Native Method Stack    │   │
│   │ (명령어 주소)│ (스택 프레임)│  (JNI 코드 실행)        │   │
│   └─────────────┴─────────────┴─────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
구분영역특징
스레드 공유Heap, Method AreaJVM 시작 시 생성, 모든 스레드가 공유
스레드 프라이빗PC, JVM Stack, Native Stack스레드 생성/종료와 함께 생성/소멸

1. 프로그램 카운터 (PC Register)

현재 실행 중인 바이트코드 명령어의 주소를 저장하는 작은 메모리 영역이다.

왜 필요한가?

CPU는 한 번에 하나의 스레드만 실행할 수 있다. 멀티스레드 환경에서 스레드가 전환될 때, 이전에 어디까지 실행했는지 기억해야 다시 이어서 실행할 수 있다. PC가 그 역할을 한다.

특징

항목설명
스레드 프라이빗각 스레드마다 독립적으로 존재
Java 메서드 실행 시현재 바이트코드 명령어 주소 저장
Native 메서드 실행 시Undefined (정의되지 않음)
예외OOM이 발생하지 않는 유일한 영역
Thread 1: PC → 0x0012 (invokevirtual)
Thread 2: PC → 0x0045 (istore_2)

스레드 전환 시 각자의 PC 값으로 실행 위치 복원

2. JVM 스택 (Java Virtual Machine Stack)

자바 메서드 실행을 위한 메모리 영역이다. 메서드 호출마다 스택 프레임이 생성되어 push되고, 메서드 종료 시 pop된다.

스택 프레임 구성

┌───────────────────────────────────────────┐
│           스택 프레임 (Stack Frame)         │
├───────────────────────────────────────────┤
│  1. 지역 변수 테이블 (Local Variable Table) │
│     - 기본 타입, 객체 참조, 반환 주소       │
│     - 컴파일 타임에 크기 결정               │
├───────────────────────────────────────────┤
│  2. 피연산자 스택 (Operand Stack)          │
│     - 연산 중간 결과 임시 저장              │
├───────────────────────────────────────────┤
│  3. 동적 링크 (Dynamic Linking)            │
│     - 런타임 상수 풀의 메서드 참조 연결     │
├───────────────────────────────────────────┤
│  4. 반환 주소 (Return Address)             │
│     - 메서드 종료 후 돌아갈 위치            │
└───────────────────────────────────────────┘

지역 변수 슬롯

지역 변수 테이블은 슬롯(Slot) 단위로 데이터를 저장한다.

public void method(int a, long b, Object c) {
    double d = 3.14;
}

// 슬롯 배치:
// [0] this  - 인스턴스 메서드는 0번에 this
// [1] a     - int (1슬롯)
// [2-3] b   - long (2슬롯)
// [4] c     - Object 참조 (1슬롯)
// [5-6] d   - double (2슬롯)
타입슬롯 수
int, float, reference1
long, double2

관련 예외

예외발생 조건
StackOverflowError스택 깊이가 허용 한계 초과 (무한 재귀 등)
OutOfMemoryError스택 확장 시 메모리 부족
// StackOverflowError 예시
public void recursive() {
    recursive();  // 무한 재귀 → StackOverflowError
}

3. 네이티브 메서드 스택 (Native Method Stack)

JNI(Java Native Interface)를 통해 호출되는 C/C++ 네이티브 코드를 실행하기 위한 스택이다.

항목설명
용도네이티브 메서드 실행
HotSpot JVMJVM 스택과 통합하여 관리
예외StackOverflowError, OutOfMemoryError

4. 힙 (Heap)

거의 모든 객체 인스턴스가 할당되는 영역이다. GC(Garbage Collection)의 주요 대상이다.

세대별 구조

힙은 **세대별 수집 이론(Generational Collection)**에 따라 영역을 나눈다.

┌─────────────────────────────────────────────────────────────┐
│                           Heap                              │
├──────────────────────────────┬──────────────────────────────┤
│       Young Generation       │       Old Generation         │
│           (1/3)              │           (2/3)              │
│  ┌─────────────────────────┐ │                              │
│  │         Eden (8)        │ │                              │
│  ├────────────┬────────────┤ │         Tenured              │
│  │  S0 (1)    │   S1 (1)   │ │    (장기 생존 객체)          │
│  └────────────┴────────────┘ │                              │
└──────────────────────────────┴──────────────────────────────┘
영역설명
Eden새 객체가 생성되는 곳
Survivor (S0, S1)Minor GC에서 살아남은 객체가 이동
Old (Tenured)여러 번 GC를 견딘 객체가 승격(Promotion)

TLAB (Thread Local Allocation Buffer)

멀티스레드 환경에서 동기화 오버헤드 없이 객체를 빠르게 할당하기 위한 기법이다.

각 스레드가 Eden 내에 자신만의 할당 버퍼를 가짐
→ TLAB 내에서는 동기화 없이 할당
→ TLAB이 차면 새 TLAB 할당 (이때만 동기화)

주요 JVM 옵션

-Xms512m          # 초기 힙 크기
-Xmx1024m         # 최대 힙 크기
-XX:NewRatio=2    # Young:Old = 1:2
-XX:SurvivorRatio=8  # Eden:Survivor = 8:1

5. 메서드 영역 (Method Area)

클래스 메타데이터, 상수, 정적 변수, JIT 컴파일 코드를 저장한다. Non-Heap 영역이라고도 부른다.

PermGen → Metaspace 변화

구분PermGen (JDK 7 이전)Metaspace (JDK 8 이후)
위치JVM 힙 내부네이티브 메모리
크기고정 (MaxPermSize)기본 무제한
String PoolPermGen 내부Heap으로 이동
OOM 메시지PermGen spaceMetaspace

JDK 8에서 PermGen이 제거된 이유

  • 고정 크기로 인한 OOM 빈번 발생
  • 클래스 메타데이터 크기 예측 어려움
  • Metaspace는 네이티브 메모리를 사용하여 유연하게 확장

주요 JVM 옵션 (JDK 8+)

-XX:MetaspaceSize=128m      # 초기 크기
-XX:MaxMetaspaceSize=256m   # 최대 크기 (기본: 무제한)

6. 런타임 상수 풀 (Runtime Constant Pool)

메서드 영역의 일부로, 클래스 파일의 **상수 풀(Constant Pool)**이 런타임에 로드되는 영역이다.

저장 내용

  • 리터럴: 숫자, 문자열 상수
  • 심벌 참조: 클래스, 필드, 메서드의 이름과 디스크립터

동적 특성

컴파일타임 상수만 저장하는 클래스 파일의 상수 풀과 달리, 런타임에 새로운 상수를 추가할 수 있다.

String s = new StringBuilder("Hello").append("World").toString();
s.intern();  // 런타임에 "HelloWorld" 상수 풀에 추가

정리

영역스레드 공유주요 저장 내용발생 가능 예외
PC RegisterX현재 명령어 주소없음
JVM StackX스택 프레임 (지역변수, 피연산자)SOF, OOM
Native Method StackX네이티브 메서드 프레임SOF, OOM
HeapO객체 인스턴스OOM
Method AreaO클래스 메타데이터, 상수, 정적 변수OOM
Runtime Constant PoolO리터럴, 심벌 참조OOM

OOM 발생 시 확인 사항

  • Heap OOM: 메모리 누수 또는 힙 크기 부족
  • Metaspace OOM: 동적 클래스 생성 과다 (프록시, CGLib 등)
  • Stack Overflow: 재귀 호출 깊이 또는 스택 프레임 크기

참고