전체 목록
JavaMedium#051

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

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

답변 포인트

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

정답 및 해설

빠른 요약

JVM 런타임 데이터 영역: Method Area (메서드 영역): 클래스 메타데이터, static 변수, 상수 풀 저장. 모든 스레드가 공유.

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

JVM 전체 구조

Java

                            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 사용, 동적으로 크기 조정

저장 내용

Java
// 아래 코드 실행 시 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 튜닝

Bash
# 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 구조 상세

Java
Heap
 Young Generation (  )
    Eden Space      :     
    Survivor 0 (S0) : Minor GC    
    Survivor 1 (S1) : Minor GC    

 Old Generation ( )
     Tenured Space   : Young Gen   
Java
// 객체 생성 - 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 메모리 설정

Bash
# 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
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: 재귀 호출이 너무 깊을 때 발생

스택 프레임 구성

Java
 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()               
   ...                            
Java
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
}

스택 크기 설정

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

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

PC 레지스터 (Program Counter Register)

특징

  • 각 스레드마다 별도 존재
  • 현재 스레드가 실행 중인 JVM 명령어(bytecode)의 주소 저장
  • 네이티브 메서드 실행 중에는 undefined
Java
// 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)를 통해 호출
Java
// 네이티브 메서드 선언
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 스레드 독립 요약

Java
// 공유 영역 (스레드 안전 이슈 발생 가능)
// - 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 메모리 모니터링

Bash
# 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

관련 질문

같은 카테고리/태그 기준