JVM 메모리 구조와 각 영역의 역할

한 줄 정의

JVM은 Java 코드를 바이트코드로 컴파일한 뒤, class loading, 메모리 할당, 실행, GC를 거쳐 프로그램을 실행하는 가상 머신이다.

실무에서 왜 중요한가

JVM 구조를 모르면 다음 상황에서 원인을 파악할 수 없다.

  • OutOfMemoryError: Java heap space 발생 시 heap 크기와 객체 생존 패턴을 분석해야 한다.
  • StackOverflowError 발생 시 재귀 호출 깊이와 스택 크기의 관계를 알아야 한다.
  • OutOfMemoryError: Metaspace 발생 시 class loading 구조를 이해해야 한다.
  • 메모리 누수가 의심될 때 heap dump를 분석할 수 있어야 한다.
  • JVM 옵션(-Xmx, -Xms, -XX:MetaspaceSize)을 설정할 때 각 영역의 의미를 알아야 한다.

Runtime Data Area 구조

JVM이 프로그램을 실행하면서 사용하는 메모리 영역이다.

┌───────────────────────────────────────┐
│         Method Area (Metaspace)       │  ← 클래스 메타데이터, 상수 풀
├───────────────────────────────────────┤
│              Heap                     │  ← 객체 인스턴스, 배열
│  ┌─────────────┬─────────────────┐    │
│  │   Young Gen  │    Old Gen      │    │
│  │ Eden│S0│S1  │                  │    │
│  └─────────────┴─────────────────┘    │
├───────────────────────────────────────┤
│   Stack (스레드마다 독립)              │  ← 메서드 호출 프레임, 지역 변수
├───────────────────────────────────────┤
│   PC Register (스레드마다 독립)        │  ← 현재 실행 중인 명령어 주소
├───────────────────────────────────────┤
│   Native Method Stack                 │  ← JNI 네이티브 메서드 호출
└───────────────────────────────────────┘

각 영역과 실무 연결

Heap

모든 객체가 생성되는 영역이다. GC의 대상이 된다.

구분설명실무 관련
Young Generation새로 생성된 객체Minor GC 대상, 대부분 여기서 사라짐
Old GenerationYoung에서 살아남은 객체Major GC/Full GC 대상, 크기가 크면 GC 시간 증가
# Heap 크기 설정
java -Xms512m -Xmx2g -jar app.jar
 
# -Xms: 초기 heap 크기
# -Xmx: 최대 heap 크기

-Xms-Xmx를 같은 값으로 설정하면 heap 확장/축소에 따른 오버헤드를 줄일 수 있다. 컨테이너 환경에서는 이렇게 설정하는 경우가 많다.

Stack

스레드마다 독립적으로 존재하며, 메서드 호출 시 프레임이 push되고 반환 시 pop된다.

상황증상
재귀 호출이 너무 깊다StackOverflowError
스레드 수가 너무 많다메모리 부족 (스레드당 stack 메모리 소비)
# 스레드 스택 크기 설정
java -Xss512k -jar app.jar

Method Area (Metaspace)

클래스 메타데이터, 런타임 상수 풀이 저장되는 영역이다. Java 8+에서 static 변수의 참조는 Heap의 java.lang.Class 객체에 저장된다.

Java 8부터 PermGen이 Metaspace로 대체되었다. Metaspace는 Native Memory를 사용하므로 heap과 별도로 관리된다.

상황증상
동적 클래스 생성이 많다 (리플렉션, 프록시)OutOfMemoryError: Metaspace
클래스 로더가 해제되지 않는다Metaspace 누수
# Metaspace 크기 설정
java -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -jar app.jar

Class Loading 과정

.java → javac → .class → ClassLoader → JVM 메모리
  1. Loading: .class 파일을 읽어서 Method Area에 클래스 정보를 저장한다.
  2. Linking: 검증(Verify) → 준비(Prepare, static 변수 기본값) → 해석(Resolve, 심볼릭 참조를 실제 참조로). Resolution은 필요 시점에 지연될 수 있다.
  3. Initialization: static 초기화 블록과 static 변수에 실제 값 할당

ClassLoader 계층 구조:

ClassLoader역할
Bootstrapjava.lang 등 JDK 핵심 클래스 로딩
Platform (Extension)JDK 확장 모듈 로딩
Applicationclasspath의 애플리케이션 클래스 로딩

JIT Compiler

JVM은 바이트코드를 인터프리터로 실행하다가, 자주 호출되는 코드(hot spot)를 네이티브 코드로 컴파일한다.

관점실무 영향
워밍업애플리케이션 시작 직후에는 JIT 최적화가 안 되어 있어 느릴 수 있다
최적화 해제런타임 조건이 바뀌면 최적화가 해제(deoptimization)될 수 있다
컨테이너 환경짧은 생명주기의 컨테이너에서는 JIT 효과를 못 볼 수 있다

실무 트러블슈팅 도구

도구용도
jps실행 중인 JVM 프로세스 목록
jstat -gc <pid>GC 통계 실시간 모니터링
jmap -dump:format=b,file=heap.hprof <pid>heap dump 생성
jstack <pid>thread dump 생성
Eclipse MAT, VisualVMheap dump 분석
Arthas운영 중인 JVM에 붙어서 실시간 진단

자주 나는 실수

  • -Xmx를 컨테이너 메모리 한도와 같게 설정해서 OOM Killer에 의해 프로세스가 죽는다.
  • heap dump 분석 없이 -Xmx만 올려서 메모리 누수를 임시로 가린다.
  • 스레드 수를 과도하게 늘려서 스택 메모리 전체 합이 시스템 메모리를 넘긴다.
  • Metaspace 크기를 설정하지 않아서 동적 프록시 생성 시 무한 증가한다.

핵심 요약

JVM의 Runtime Data Area는 크게 Heap, Stack, Method Area(Metaspace)로 나뉩니다. Heap은 모든 객체가 생성되는 영역으로 GC의 대상이 되고, Stack은 스레드마다 독립적으로 메서드 호출 프레임을 관리합니다. Metaspace는 Java 8부터 PermGen을 대체한 영역으로 클래스 메타데이터를 저장합니다.

실무에서는 -Xmx로 Heap 크기를, -Xss로 스레드 스택 크기를, -XX:MaxMetaspaceSize로 Metaspace 크기를 설정합니다. 컨테이너 환경에서는 JVM 메모리 총합이 컨테이너 한도를 넘지 않도록 주의해야 하고, 메모리 문제 발생 시 heap dump와 jstat 같은 도구로 분석합니다.

꼬리 질문

점검 퀴즈

아래 문항은 개념을 실제로 설명할 수 있는지 점검하기 위한 것이다. 선택지를 누르면 정답 여부와 이유가 표시된다.

객관식Heap과 Stack의 차이로 가장 적절한 설명은?

OX컨테이너에서 -Xmx를 컨테이너 메모리 한도와 같게 잡으면 안전하다.

객관식JIT 워밍업이 실무에서 문제가 되는 상황은?

관련 문서