자동 메모리 관리
자동 메모리 관리
JVM의 런타임 데이터 영역, 객체 생성 과정, 메모리 오버플로 예외를 다룬다.
1. 런타임 데이터 영역
JVM은 자바 프로그램 실행 중 필요한 메모리를 여러 영역으로 나누어 관리한다.
┌─────────────────────────────────────────────────────────────────────────────┐
│ JVM 런타임 데이터 영역 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 스레드 공유 영역 │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ 힙 (Heap) │ │ │
│ │ │ ┌──────────────────┐ ┌──────────────────────────────┐ │ │ │
│ │ │ │ Young Gen │ │ Old Gen │ │ │ │
│ │ │ │ ┌─────┬────┬────┐│ │ │ │ │ │
│ │ │ │ │Eden │ S0 │ S1 ││ │ (Tenured) │ │ │ │
│ │ │ │ └─────┴────┴────┘│ │ │ │ │ │
│ │ │ └──────────────────┘ └──────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ 메서드 영역 (Method Area) │ │ │
│ │ │ JDK 7: PermGen / JDK 8+: Metaspace │ │ │
│ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ 런타임 상수 풀 (Runtime Constant Pool) │ │ │ │
│ │ │ └─────────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 스레드 프라이빗 영역 │ │
│ │ │ │
│ │ Thread 1 Thread 2 Thread 3 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ PC │ │ PC │ │ PC │ │ │
│ │ ├──────────┤ ├──────────┤ ├──────────┤ │ │
│ │ │ JVM Stack│ │ JVM Stack│ │ JVM Stack│ │ │
│ │ ├──────────┤ ├──────────┤ ├──────────┤ │ │
│ │ │ Native │ │ Native │ │ Native │ │ │
│ │ │ Method │ │ Method │ │ Method │ │ │
│ │ │ Stack │ │ Stack │ │ Stack │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 다이렉트 메모리 (Direct Memory) │ │
│ │ (JVM 외부, 네이티브 메모리) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘1.1 프로그램 카운터 (PC Register)
현재 실행 중인 바이트코드의 줄 번호(주소)를 저장하는 작은 메모리 영역이다.
| 특징 | 설명 |
|---|---|
| 스레드 프라이빗 | 각 스레드마다 독립적으로 존재 |
| 목적 | 다음에 실행할 명령어 위치 저장 |
| 예외 | OutOfMemoryError가 발생하지 않는 유일한 영역 |
┌─────────────────────────────────────────────────────────────────┐
│ 프로그램 카운터 동작 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Thread 1 Thread 2 │
│ PC: 0x0012 PC: 0x0045 │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 바이트코드 │ │ 바이트코드 │ │
│ │ 0x0010: aload_0│ │ 0x0043: iload_1│ │
│ │ 0x0011: getfield│ │ 0x0044: iadd │ │
│ │►0x0012: invokevirtual│ │►0x0045: istore_2│ │
│ │ 0x0015: return │ │ 0x0046: return │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 스레드 전환 시 PC 값으로 이전 실행 위치 복원 │
│ │
└─────────────────────────────────────────────────────────────────┘1.2 JVM 스택 (Java Virtual Machine Stack)
자바 메서드 실행을 위한 스레드별 메모리 영역이다.
┌─────────────────────────────────────────────────────────────────────────┐
│ JVM 스택 구조 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ JVM Stack │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ Stack Frame (method C) │ │ ← Top │
│ │ │ ┌──────────────┬──────────────────────┐ │ │ │
│ │ │ │ 지역변수 테이블 │ 피연산자 스택 │ │ │ │
│ │ │ ├──────────────┼──────────────────────┤ │ │ │
│ │ │ │ 동적 링크 │ 반환 주소 │ │ │ │
│ │ │ └──────────────┴──────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ Stack Frame (method B) │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ Stack Frame (method A) │ │ ← Bottom │
│ │ └───────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 메서드 호출 시 push, 메서드 종료 시 pop │
│ │
└─────────────────────────────────────────────────────────────────────────┘스택 프레임 구성요소
| 구성요소 | 설명 |
|---|---|
| 지역 변수 테이블 | 기본 타입, 객체 참조, 반환 주소 저장 (슬롯 단위) |
| 피연산자 스택 | 연산 중간 결과 임시 저장 |
| 동적 링크 | 런타임 상수 풀의 메서드 참조 연결 |
| 반환 주소 | 메서드 종료 후 돌아갈 위치 |
지역 변수 슬롯
┌─────────────────────────────────────────────────────────────────┐
│ 지역 변수 테이블 예시 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ public void method(int a, long b, Object c) { │
│ double d = 3.14; │
│ } │
│ │
│ 슬롯 인덱스: │
│ ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │
│ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ │
│ ├──────┼──────┼──────┴──────┼──────┼──────┴──────┤ │
│ │ this │ a │ b │ c │ d │ │
│ │(ref) │(int) │ (long) │(ref) │ (double) │ │
│ │ 32bit│ 32bit│ 64bit │ 32bit│ 64bit │ │
│ └──────┴──────┴─────────────┴──────┴─────────────┘ │
│ │
│ • 32비트 타입: 슬롯 1개 (int, float, reference) │
│ • 64비트 타입: 슬롯 2개 (long, double) │
│ • 인스턴스 메서드: 슬롯 0에 this 참조 │
│ │
└─────────────────────────────────────────────────────────────────┘스택 관련 예외
| 예외 | 발생 조건 |
|---|---|
| StackOverflowError | 스택 깊이가 허용 한계 초과 (재귀 호출 등) |
| OutOfMemoryError | 스택 확장 시 메모리 부족 |
// StackOverflowError 예제
public class StackOverflowTest {
private int depth = 0;
public void recursiveCall() {
depth++;
recursiveCall(); // 무한 재귀 → StackOverflowError
}
public static void main(String[] args) {
StackOverflowTest test = new StackOverflowTest();
try {
test.recursiveCall();
} catch (StackOverflowError e) {
System.out.println("Stack depth: " + test.depth);
}
}
}1.3 네이티브 메서드 스택
JNI(Java Native Interface)를 통해 호출되는 네이티브 메서드용 스택이다.
| 특징 | 설명 |
|---|---|
| 용도 | C/C++ 등 네이티브 코드 실행 |
| HotSpot | JVM 스택과 네이티브 스택 통합 |
| 예외 | StackOverflowError, OutOfMemoryError |
1.4 힙 (Heap)
객체 인스턴스를 저장하는 JVM 최대 메모리 영역이다.
┌─────────────────────────────────────────────────────────────────────────────┐
│ 힙 메모리 구조 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Heap │ │
│ │ │ │
│ │ ┌───────────────────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ Young Generation │ │ Old Generation │ │ │
│ │ │ (1/3) │ │ (2/3) │ │ │
│ │ │ │ │ │ │ │
│ │ │ ┌─────────────────────┐ │ │ │ │ │
│ │ │ │ Eden │ │ │ │ │ │
│ │ │ │ (8) │ │ │ Tenured │ │ │
│ │ │ └─────────────────────┘ │ │ │ │ │
│ │ │ ┌─────────┬───────────┐ │ │ (장기 생존 객체) │ │ │
│ │ │ │ From(S0)│ To(S1) │ │ │ │ │ │
│ │ │ │ (1) │ (1) │ │ │ │ │ │
│ │ │ └─────────┴───────────┘ │ │ │ │ │
│ │ └───────────────────────────┘ └─────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Young:Old = 1:2 (기본값) │
│ Eden:S0:S1 = 8:1:1 (기본값) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘힙 영역 설명
| 영역 | 설명 |
|---|---|
| Eden | 새로 생성된 객체가 할당되는 영역 |
| Survivor (S0, S1) | Minor GC에서 살아남은 객체가 이동하는 영역 |
| Old (Tenured) | 오래 살아남은 객체가 이동하는 영역 |
힙 관련 JVM 옵션
# 힙 크기 설정
-Xms512m # 초기 힙 크기
-Xmx1024m # 최대 힙 크기
# 세대별 크기 설정
-XX:NewRatio=2 # Old:Young = 2:1
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1
# OOM 시 힙 덤프
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump1.5 메서드 영역 (Method Area)
클래스 메타데이터, 상수, 정적 변수, JIT 컴파일 코드를 저장한다.
┌─────────────────────────────────────────────────────────────────────────┐
│ 메서드 영역 구조 변화 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [JDK 7 이전: PermGen (영구 세대)] │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ • 클래스 메타데이터 │ │
│ │ • 상수 풀 (String 리터럴 포함) │ │
│ │ • 정적 변수 │ │
│ │ • JIT 컴파일된 코드 │ │
│ │ │ │
│ │ 크기 제한: -XX:PermSize, -XX:MaxPermSize │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ [JDK 8 이후: Metaspace] │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ • 클래스 메타데이터 │ │
│ │ • JIT 컴파일된 코드 │ │
│ │ │ │
│ │ 네이티브 메모리 사용 (기본적으로 무제한) │ │
│ │ 크기 제한: -XX:MetaspaceSize, -XX:MaxMetaspaceSize │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ※ JDK 7+: String 상수 풀 → Heap으로 이동 │
│ ※ JDK 8+: 정적 변수 → Heap으로 이동 │
│ │
└─────────────────────────────────────────────────────────────────────────┘PermGen vs Metaspace
| 구분 | PermGen (JDK 7-) | Metaspace (JDK 8+) |
|---|---|---|
| 위치 | JVM 힙 내부 | 네이티브 메모리 |
| 크기 | 고정 (-XX:MaxPermSize) | 기본 무제한 |
| String Pool | PermGen 내부 | Heap으로 이동 |
| OOM 메시지 | PermGen space | Metaspace |
1.6 런타임 상수 풀 (Runtime Constant Pool)
클래스 파일의 상수 풀이 런타임에 로드되는 영역이다.
┌─────────────────────────────────────────────────────────────────────────┐
│ 런타임 상수 풀 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 클래스 파일 (.class) 런타임 상수 풀 │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ Constant Pool │ 로딩 │ │ │
│ │ (컴파일타임 상수) │ ───────► │ • 리터럴 (숫자, 문자열) │ │
│ │ │ │ • 심벌 참조 │ │
│ │ • 클래스 이름 │ │ • 직접 참조 (런타임 해석) │ │
│ │ • 필드 이름/타입 │ │ │ │
│ │ • 메서드 이름/시그니처│ │ ※ 동적으로 추가 가능 │ │
│ │ • 리터럴 값 │ │ (String.intern() 등) │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘String.intern() 동작 변화
public class StringInternTest {
public static void main(String[] args) {
String str1 = new StringBuilder("Hello").append("World").toString();
// JDK 6: false (PermGen에 복사본 생성)
// JDK 7+: true (Heap의 기존 참조 반환)
System.out.println(str1.intern() == str1);
// "java"는 이미 상수 풀에 존재 → false
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2); // false
}
}1.7 다이렉트 메모리 (Direct Memory)
JVM 외부의 네이티브 메모리로, NIO에서 주로 사용한다.
// DirectByteBuffer 사용 예제
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024); // 1MB
// JVM 옵션
// -XX:MaxDirectMemorySize=256m| 특징 | 설명 |
|---|---|
| 위치 | JVM 힙 외부 (네이티브 메모리) |
| 장점 | 시스템 콜 없이 직접 I/O 가능 |
| 주의 | 힙과 별개로 메모리 관리 필요 |
2. 객체 생성 과정
2.1 객체 생성 단계
┌─────────────────────────────────────────────────────────────────────────────┐
│ 객체 생성 과정 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Java 코드 바이트코드 │
│ Object obj = new Object(); → new #2 <java/lang/Object> │
│ invokespecial #1 <init> │
│ │
│ ───────────────────────────────────────────────────────────────────── │
│ │
│ 1. 클래스 로딩 확인 │
│ │ 상수 풀에서 클래스 심벌 참조 확인 │
│ │ 클래스가 로드/해석/초기화 되었는지 확인 │
│ ▼ │
│ 2. 메모리 할당 │
│ │ 객체 크기 계산 (클래스 로딩 시 결정됨) │
│ │ 힙에서 메모리 할당 (Bump the Pointer 또는 Free List) │
│ │ 스레드 안전: CAS 또는 TLAB │
│ ▼ │
│ 3. 메모리 초기화 (Zero 초기화) │
│ │ 할당받은 공간을 0으로 초기화 │
│ │ (인스턴스 필드의 기본값 보장) │
│ ▼ │
│ 4. 객체 헤더 설정 │
│ │ Mark Word, Klass Pointer 설정 │
│ │ 해시코드, GC 세대, 락 상태 등 │
│ ▼ │
│ 5. 생성자 실행 (<init>) │
│ │ 개발자가 정의한 초기화 수행 │
│ ▼ │
│ 6. 객체 참조 반환 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘2.2 메모리 할당 방식
┌─────────────────────────────────────────────────────────────────────────────┐
│ 메모리 할당 방식 비교 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ [포인터 밀치기 (Bump the Pointer)] │
│ 규칙적인 힙 구조 (Compact GC: Serial, ParNew) │
│ │
│ 할당 전: 할당 후: │
│ ┌──────────────────┬─────────────┐ ┌──────────────────┬────┬──────────┐ │
│ │ 사용 중 │ 여유 공간 │ │ 사용 중 │새객│ 여유공간 │ │
│ └──────────────────┴─────────────┘ └──────────────────┴────┴──────────┘ │
│ ▲ ▲ │
│ pointer pointer │
│ │
│ ───────────────────────────────────────────────────────────────────── │
│ │
│ [여유 목록 (Free List)] │
│ 불규칙적인 힙 구조 (Sweep GC: CMS) │
│ │
│ ┌────┬────────┬────┬──────┬────┬────────────┬────┐ │
│ │사용│ 여유 │사용│ 여유 │사용│ 여유 │사용│ │
│ └────┴────────┴────┴──────┴────┴────────────┴────┘ │
│ ▲ ▲ ▲ │
│ └────────────┴──────────────┘ │
│ 여유 목록으로 관리 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘2.3 스레드 안전한 할당
| 방법 | 설명 |
|---|---|
| CAS + 재시도 | 포인터 갱신을 원자적으로 수행 |
| TLAB | 스레드별 로컬 할당 버퍼 사용 |
┌─────────────────────────────────────────────────────────────────────────┐
│ TLAB (Thread Local Allocation Buffer) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Heap │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ │
│ │ │ Thread 1 │ │ Thread 2 │ │ Thread 3 │ │ 공유 영역 │ │ │
│ │ │ TLAB │ │ TLAB │ │ TLAB │ │ │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ • 각 스레드가 자신만의 작은 할당 영역 보유 │
│ • TLAB 내에서는 동기화 없이 할당 │
│ • TLAB 부족 시에만 동기화하여 새 TLAB 할당 │
│ • -XX:+/-UseTLAB 로 설정 │
│ │
└─────────────────────────────────────────────────────────────────────────┘3. 객체 메모리 레이아웃
3.1 객체 구조
┌─────────────────────────────────────────────────────────────────────────────┐
│ HotSpot 객체 메모리 레이아웃 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Object Header │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Mark Word (8 bytes on 64-bit) │ │ │
│ │ │ • 해시코드 (25 bits) │ │ │
│ │ │ • GC 세대 나이 (4 bits) │ │ │
│ │ │ • 락 상태 플래그 (2 bits) │ │ │
│ │ │ • 편향 락 정보 │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Klass Pointer (4 or 8 bytes) │ │ │
│ │ │ • 클래스 메타데이터 포인터 │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ Array Length (배열인 경우, 4 bytes) │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Instance Data │ │
│ │ • 필드 값들 (부모 클래스 필드 포함) │ │
│ │ • 할당 순서: long/double → int → short/char → byte/boolean → ref │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Padding (정렬 패딩) │ │
│ │ • 객체 크기를 8바이트 배수로 맞춤 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘3.2 Mark Word 상태별 구조
| 상태 | 저장 내용 | 락 플래그 |
|---|---|---|
| 잠기지 않음 | 해시코드, GC 나이 | 01 |
| 경량 락 | 락 레코드 포인터 | 00 |
| 중량 락 | 모니터 포인터 | 10 |
| GC 마크 | (비어 있음) | 11 |
| 편향 락 | 스레드 ID, 타임스탬프 | 01 |
3.3 객체 접근 방식
┌─────────────────────────────────────────────────────────────────────────────┐
│ 객체 접근 방식 비교 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ [핸들 방식] │
│ ┌─────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Stack │ │ 핸들 풀 │ │ Heap │ │
│ │ │ │ │ │ │ │
│ │ ┌─────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ ref ├─┼─────►│ │인스턴스 ptr├─┼─────►│ │ 객체 데이터 │ │ │
│ │ └─────┘ │ │ ├─────────────┤ │ │ └─────────────┘ │ │
│ └─────────┘ │ │ 타입 ptr ├─┼──┐ └─────────────────┘ │
│ │ └─────────────┘ │ │ ┌─────────────────┐ │
│ └─────────────────┘ └──►│ 클래스 메타 │ │
│ 장점: GC 시 참조 갱신 불필요 └─────────────────┘ │
│ 단점: 간접 참조로 인한 오버헤드 │
│ │
│ ───────────────────────────────────────────────────────────────────── │
│ │
│ [다이렉트 포인터 방식] (HotSpot 사용) │
│ ┌─────────┐ ┌─────────────────────────────┐ │
│ │ Stack │ │ Heap │ │
│ │ │ │ │ │
│ │ ┌─────┐ │ │ ┌─────────────────────────┐ │ │
│ │ │ ref ├─┼──────────────────────────►│ │ 객체 헤더 │ │ │
│ │ └─────┘ │ │ │ ├── Klass Pointer ───┐ │ │ │
│ └─────────┘ │ │ └────────────────────┘ │ │ │
│ │ │ 인스턴스 데이터 │ │ │
│ │ └─────────────┼───────────┘ │ │
│ 장점: 빠른 접근 속도 └───────────────┼─────────────┘ │
│ 단점: GC 시 참조 갱신 필요 ▼ │
│ ┌───────────────────────────────┐ │
│ │ 클래스 메타데이터 │ │
│ └───────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘4. OutOfMemoryError 예외
4.1 힙 오버플로
/**
* VM 옵션: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {
static class OOMObject {}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
}
}
// 결과: java.lang.OutOfMemoryError: Java heap space해결 방법
┌─────────────────────────────────────────────────────────────────────────┐
│ 힙 OOM 분석 및 해결 흐름 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ OutOfMemoryError: Java heap space │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ 힙 덤프 분석 │ │
│ │ (MAT, VisualVM 등) │ │
│ └───────────┬───────────┘ │
│ │ │
│ ┌──────────────┴──────────────┐ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ 메모리 누수 │ │ 메모리 부족 │ │
│ │ (불필요 객체) │ │ (필요 객체) │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ • GC Root 참조 체인 분석 • -Xmx 증가 검토 │
│ • 메모리 누수 코드 수정 • 객체 수명 최적화 │
│ • 데이터 구조 최적화 │
│ │
└─────────────────────────────────────────────────────────────────────────┘4.2 스택 오버플로
/**
* VM 옵션: -Xss256k
*/
public class StackSOF {
private int stackLength = 0;
public void stackLeak() {
stackLength++;
stackLeak(); // 무한 재귀
}
public static void main(String[] args) {
StackSOF sof = new StackSOF();
try {
sof.stackLeak();
} catch (StackOverflowError e) {
System.out.println("Stack length: " + sof.stackLength);
// 결과: java.lang.StackOverflowError
}
}
}4.3 메서드 영역 오버플로
/**
* VM 옵션 (JDK 8+): -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*/
public class MetaspaceOOM {
public static void main(String[] args) {
while (true) {
// CGLib 등으로 동적 클래스 생성
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy)
-> proxy.invokeSuper(obj, args1));
enhancer.create();
}
}
static class OOMObject {}
}
// 결과: java.lang.OutOfMemoryError: Metaspace4.4 OOM 유형 요약
| 영역 | 에러 메시지 | 주요 원인 |
|---|---|---|
| Heap | Java heap space | 객체 과다 생성, 메모리 누수 |
| Metaspace | Metaspace | 동적 클래스 과다 생성 |
| Stack | StackOverflowError | 깊은 재귀, 큰 스택 프레임 |
| Direct Memory | Direct buffer memory | NIO 버퍼 과다 사용 |
5. JVM 메모리 옵션 정리
# 힙 메모리
-Xms512m # 초기 힙 크기
-Xmx1024m # 최대 힙 크기
-XX:NewRatio=2 # Young:Old 비율
-XX:SurvivorRatio=8 # Eden:Survivor 비율
# 메타스페이스 (JDK 8+)
-XX:MetaspaceSize=128m # 초기 메타스페이스 크기
-XX:MaxMetaspaceSize=256m # 최대 메타스페이스 크기
# 스택
-Xss256k # 스레드 스택 크기
# 다이렉트 메모리
-XX:MaxDirectMemorySize=256m
# GC 로깅 및 힙 덤프
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump
-Xlog:gc*:file=gc.log # JDK 9+6. 핵심 요약
┌─────────────────────────────────────────────────────────────────────────────┐
│ JVM 메모리 관리 요약 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 런타임 데이터 영역 │
│ ├── 스레드 공유: Heap, Method Area (Metaspace) │
│ └── 스레드 프라이빗: PC Register, JVM Stack, Native Method Stack │
│ │
│ 객체 생성 과정 │
│ ├── 1. 클래스 로딩 확인 │
│ ├── 2. 메모리 할당 (Bump the Pointer / Free List) │
│ ├── 3. 메모리 초기화 (Zero) │
│ ├── 4. 객체 헤더 설정 │
│ └── 5. 생성자(<init>) 실행 │
│ │
│ 객체 레이아웃 │
│ ├── Object Header: Mark Word + Klass Pointer (+ Array Length) │
│ ├── Instance Data: 필드 값들 │
│ └── Padding: 8바이트 정렬 │
│ │
│ OOM 예외 │
│ ├── Heap: Java heap space │
│ ├── Metaspace: Metaspace (JDK 8+) │
│ ├── Stack: StackOverflowError │
│ └── Direct: Direct buffer memory │
│ │
└─────────────────────────────────────────────────────────────────────────────┘