전체 목록
JavaMedium#51

JVM의 구조(메모리 영역)를 설명해주세요.

#Java#JVM#메모리#핵심개념
힌트

Method Area, Heap, Stack, PC Register, Native Method Stack을 떠올려보세요.

정답 및 해설

JVM의 구조(메모리 영역)를 설명해주세요.

JVM(Java Virtual Machine)은 Java 바이트코드를 실행하는 가상 머신으로, "Write Once, Run Anywhere"를 가능하게 하는 핵심 요소입니다. JVM은 런타임 시 다양한 메모리 영역을 사용하며, 각 영역은 특정 목적에 맞게 설계되어 있습니다. JVM 메모리 구조를 이해하면 OutOfMemoryError 디버깅, 성능 튜닝, GC(가비지 컬렉션) 동작 이해에 도움이 됩니다.

JVM 전체 구조

┌─────────────────────────────────────────────────────────────────┐
│                            JVM                                  │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   런타임 데이터 영역                        │  │
│  │  ┌─────────────┐  ┌─────────────────────────────────┐   │  │
│  │  │ Method Area │  │            Heap                  │   │  │
│  │  │ (공유)      │  │  ┌──────────────┐ ┌──────────┐  │   │  │
│  │  │ - 클래스 정보│  │  │Young Gen     │ │ Old Gen  │  │   │  │
│  │  │ - static 변수│  │  │ Eden|S0|S1  │ │          │  │   │  │
│  │  │ - 상수 풀   │  │  └──────────────┘ └──────────┘  │   │  │
│  │  └─────────────┘  └─────────────────────────────────┘   │  │
│  │                                                           │  │
│  │  ┌──────────────┐  ┌─────────┐  ┌──────────────────┐   │  │
│  │  │   JVM Stack  │  │   PC    │  │ Native Method     │   │  │
│  │  │  (스레드별)  │  │Register │  │    Stack          │   │  │
│  │  │ [Frame]      │  │(스레드별)│  │  (스레드별)       │   │  │
│  │  └──────────────┘  └─────────┘  └──────────────────┘   │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐  │
│  │ Class Loader │  │ 실행 엔진    │  │ Native Method        │  │
│  │ Subsystem    │  │ (JIT 컴파일러│  │ Interface (JNI)      │  │
│  │              │  │  + 인터프리터)│  │                      │  │
│  └──────────────┘  └──────────────┘  └──────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

메서드 영역 (Method Area)

특징

  • 모든 스레드가 공유하는 영역
  • JVM 시작 시 생성, JVM 종료 시 해제
  • Java 8부터 "Metaspace"로 변경 (PermGen 제거)
    • PermGen: Heap의 일부였으며 크기 고정 → OutOfMemoryError: PermGen space 발생
    • Metaspace: Native Memory 사용, 동적으로 크기 조정

저장 내용

// 아래 코드 실행 시 Method Area에 저장되는 것들:

public class User {
  // static 변수 → Method Area에 저장
  private static int count = 0;
  private static final String TYPE = "USER";  // 상수 풀(Constant Pool)에 저장

  // 인스턴스 변수 → 메서드 영역에 필드 정보만, 실제 값은 Heap에
  private String name;
  private int age;

  // 메서드 바이트코드 → Method Area에 저장
  public String getName() { return name; }

  // 클래스 메타데이터:
  // - 클래스 이름, 접근 제한자
  // - 부모 클래스 참조
  // - 구현한 인터페이스 목록
  // - 필드/메서드 정보
}

// 런타임 상수 풀 (Runtime Constant Pool)
// - 리터럴 값 ("hello", 42 등)
// - 심볼릭 참조 (클래스/메서드/필드 이름)
String s1 = "hello"  // 상수 풀에서 참조
String s2 = "hello"  // 동일한 상수 풀 항목 참조
System.out.println(s1 == s2)  // true (같은 참조)

Metaspace 튜닝

# JVM 옵션
-XX:MetaspaceSize=256m      # Metaspace 초기 크기
-XX:MaxMetaspaceSize=512m   # Metaspace 최대 크기
# 설정하지 않으면 사용 가능한 Native Memory 한도까지 자동 확장

# PermGen 시절 (Java 8 이전)
-XX:PermSize=128m           # 초기 크기 (지금은 무시됨)
-XX:MaxPermSize=256m        # 최대 크기 (지금은 무시됨)

힙 (Heap)

특징

  • 모든 스레드가 공유하는 영역
  • 인스턴스 객체와 배열이 저장되는 곳
  • GC(가비지 컬렉션)의 대상 - 더 이상 참조되지 않는 객체 수거
  • Young Generation + Old Generation으로 구성

Heap 구조 상세

Heap
├── Young Generation (새로 생성된 객체들)
│   ├── Eden Space      : 새 객체가 최초 생성되는 공간
│   ├── Survivor 0 (S0) : Minor GC 생존 객체 임시 보관
│   └── Survivor 1 (S1) : Minor GC 생존 객체 임시 보관
│
└── Old Generation (오래된 객체들)
    └── Tenured Space   : Young Gen에서 오래 살아남은 객체
// 객체 생성 - Heap에 저장됨
User user = new User("김철수", 25)   // new → Eden Space에 객체 생성
int[] arr = new int[100]              // 배열도 Heap에 저장

// 참조 변수는 Stack에, 실제 객체 데이터는 Heap에
// Stack: [user → 참조값(주소)] ------→ Heap: { name:"김철수", age:25 }

// 객체가 참조를 잃으면 GC 대상
user = null  // Heap의 User 객체가 GC 대상이 됨

Heap 메모리 설정

# JVM Heap 크기 설정
-Xms512m    # 초기 Heap 크기 (최소)
-Xmx2g      # 최대 Heap 크기
-Xmn256m    # Young Generation 크기

# 권장: -Xms와 -Xmx를 동일하게 설정 → GC 오버헤드 감소
java -Xms2g -Xmx2g -jar myapp.jar

# Young/Old Generation 비율
-XX:NewRatio=2    # Old:Young = 2:1 (Young = 전체의 1/3)
-XX:SurvivorRatio=8  # Eden:Survivor = 8:1:1 (Eden = Young의 8/10)

OutOfMemoryError 유형

java.lang.OutOfMemoryError: Java heap space
→ Heap 공간 부족. -Xmx 증가 또는 메모리 누수 확인

java.lang.OutOfMemoryError: GC overhead limit exceeded
→ GC가 너무 자주 실행되어 작업 시간의 98%를 GC에 사용

java.lang.OutOfMemoryError: Metaspace
→ 클래스가 너무 많이 로드됨. -XX:MaxMetaspaceSize 증가

java.lang.OutOfMemoryError: Direct buffer memory
→ NIO Direct Buffer 부족. -XX:MaxDirectMemorySize 증가

JVM 스택 (JVM Stack)

특징

  • 각 스레드마다 별도의 Stack 생성
  • 메서드 호출 시 스택 프레임(Stack Frame) 생성, 반환 시 제거
  • 스레드 안전 (다른 스레드와 공유하지 않음)
  • StackOverflowError: 재귀 호출이 너무 깊을 때 발생

스택 프레임 구성

스레드의 JVM Stack:
┌─────────────────────────────────────┐
│  Stack Frame 3: method3()           │ ← 현재 실행 중
│  ├── 지역 변수 배열 (Local Variables)│
│  │   [0] this, [1] param1, [2] x   │
│  ├── 피연산자 스택 (Operand Stack)  │
│  └── 현재 클래스의 런타임 상수 풀 참조│
├─────────────────────────────────────┤
│  Stack Frame 2: method2()           │
│  ├── 지역 변수: [0] this, [1] y    │
│  └── 피연산자 스택                  │
├─────────────────────────────────────┤
│  Stack Frame 1: method1()           │ ← 처음 호출된 메서드
│  └── ...                            │
└─────────────────────────────────────┘
public class StackExample {
  public static void main(String[] args) {
    // 스택 프레임 1: main()
    int x = 10;           // 지역 변수 → Stack에 저장
    String name = "철수"  // 참조값 → Stack에, 실제 문자열 → Heap에

    method1(x);           // method1 호출 → 새 스택 프레임 생성
  }  // main 반환 → 스택 프레임 1 제거

  static void method1(int n) {
    // 스택 프레임 2: method1()
    int result = n * 2;   // 지역 변수 → Stack
    method2(result);
  }  // 반환 → 스택 프레임 2 제거

  static void method2(int value) {
    // 스택 프레임 3: method2()
    System.out.println(value);
  }
}

// StackOverflowError 예시
static void infiniteRecursion() {
  infiniteRecursion()  // 종료 조건 없는 재귀 → StackOverflowError
}

스택 크기 설정

# 기본 스택 크기: 플랫폼에 따라 다름 (일반적으로 512KB~1MB)
-Xss512k  # 스레드당 스택 크기 512KB
-Xss2m    # 스레드당 스택 크기 2MB

# 깊은 재귀가 필요한 경우 증가
# 단, 메모리를 많이 사용하는 멀티스레드 환경에서는 주의

PC 레지스터 (Program Counter Register)

특징

  • 각 스레드마다 별도 존재
  • 현재 스레드가 실행 중인 JVM 명령어(bytecode)의 주소 저장
  • 네이티브 메서드 실행 중에는 undefined
// PC 레지스터 동작 (내부적으로)
// 바이트코드:
// 0: iload_1      ← PC = 0
// 1: iload_2      ← PC = 1
// 2: iadd         ← PC = 2
// 3: istore_3     ← PC = 3
// 4: return

// 멀티스레드 환경에서 컨텍스트 스위칭 시
// 각 스레드의 PC 레지스터가 다음 실행할 명령어 주소를 기억함
// Thread A: PC = 2 (iadd 실행 중)
// Thread B: PC = 15 (다른 명령어 실행 중)
// → 컨텍스트 스위치 후 각 스레드가 올바른 위치에서 재개 가능

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

특징

  • 각 스레드마다 별도 존재
  • Java가 아닌 C/C++로 작성된 네이티브 메서드 실행에 사용
  • JNI(Java Native Interface)를 통해 호출
// 네이티브 메서드 선언
public class NativeExample {
  // native 키워드 - JVM Stack 아닌 Native Method Stack 사용
  public native int nativeCalculation(int a, int b)

  static {
    System.loadLibrary("myNativeLib")  // .so / .dll 파일 로드
  }
}

// java.lang.Object의 메서드들도 native
public final native Class<?> getClass()
public native int hashCode()
protected native Object clone() throws CloneNotSupportedException

스레드 공유 vs 스레드 독립 요약

// 공유 영역 (스레드 안전 이슈 발생 가능)
// - Method Area: static 변수, 클래스 메타데이터
// - Heap: 객체 인스턴스

// 예: static 변수는 모든 스레드가 공유 → 동기화 필요
class Counter {
  private static int count = 0  // Heap에? NO, Method Area에

  // 동기화 없이는 스레드 안전하지 않음
  public static synchronized void increment() {
    count++
  }
}

// 스레드별 독립 영역 (스레드 안전)
// - JVM Stack: 지역 변수, 매개변수
// - PC Register: 현재 실행 주소
// - Native Method Stack: 네이티브 메서드 호출

// 지역 변수는 스레드별 Stack에 있으므로 동기화 불필요
void processData(int value) {
  int localVar = value * 2  // Stack에 저장 → 스레드 안전
}

JVM 메모리 모니터링

# JVM 플래그로 GC 로그 확인
java -verbose:gc -XX:+PrintGCDetails -jar myapp.jar

# jstat으로 실시간 메모리 모니터링
jstat -gc <PID> 1000  # 1초 간격으로 GC 통계 출력
# S0C S1C S0U S1U EC EU OC OU MC MU ...
# Survivor0 Capacity, Usage / Eden Capacity, Usage / Old ...

# jmap으로 힙 덤프 생성
jmap -dump:format=b,file=heapdump.hprof <PID>

# jvisualvm / JMC (Java Mission Control)로 GUI 모니터링

정리 표

영역스레드 공유저장 내용GC 대상에러 유형
Method Area (Metaspace)공유클래스 메타데이터, static 변수, 상수 풀일부OutOfMemoryError: Metaspace
Heap (Eden, Survivor, Old)공유인스턴스 객체, 배열주 대상OutOfMemoryError: Java heap space
JVM Stack스레드별 독립스택 프레임(지역 변수, 매개변수)아님StackOverflowError
PC Register스레드별 독립현재 실행 중인 바이트코드 주소아님-
Native Method Stack스레드별 독립네이티브 메서드 호출 정보아님StackOverflowError