자동 메모리 관리

자동 메모리 관리

자동 메모리 관리

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++ 등 네이티브 코드 실행
HotSpotJVM 스택과 네이티브 스택 통합
예외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/dump

1.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 PoolPermGen 내부Heap으로 이동
OOM 메시지PermGen spaceMetaspace

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: Metaspace

4.4 OOM 유형 요약

영역에러 메시지주요 원인
HeapJava heap space객체 과다 생성, 메모리 누수
MetaspaceMetaspace동적 클래스 과다 생성
StackStackOverflowError깊은 재귀, 큰 스택 프레임
Direct MemoryDirect buffer memoryNIO 버퍼 과다 사용

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                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘