3. Runtime Data Areas

3.1 Method Area (Metaspace - Java 8+)

저장 내용:

  • 클래스 메타데이터 (클래스명, 부모 클래스, 인터페이스)
  • static 변수
  • 메서드 정보 (메서드명, 반환 타입, 파라미터)
  • 상수 풀 (Constant Pool)
public class MethodAreaExample {
    // Method Area에 저장
    static int staticVar = 100;
    static final String CONSTANT = "Hello";

    public void method() {
        // 메서드 정보도 Method Area에 저장
    }
}

Java 7 vs Java 8 변화:

Java 7 (PermGen):
┌─────────────┐
│   Heap      │
├─────────────┤
│   PermGen   │  ← 고정 크기 (-XX:PermSize, -XX:MaxPermSize)
│  - Classes  │     OutOfMemoryError: PermGen space
│  - Strings  │
└─────────────┘

Java 8+ (Metaspace):
┌─────────────┐
│   Heap      │  ← String Pool 이동
└─────────────┘
┌─────────────┐
│  Metaspace  │  ← Native Memory (동적 크기)
│  - Classes  │     -XX:MetaspaceSize, -XX:MaxMetaspaceSize
└─────────────┘

JVM 옵션:

# Java 8+
-XX:MetaspaceSize=128m      # 초기 크기
-XX:MaxMetaspaceSize=512m   # 최대 크기

# Metaspace 모니터링
jstat -gc <pid>

3.2 Heap (힙 영역)

구조 (Generational Heap):

┌─────────────────────────────────────────────────────────┐
│                        Heap                              │
│                                                          │
│  ┌──────────────────────────┐  ┌──────────────────────┐ │
│  │      Young Generation     │  │   Old Generation     │ │
│  │                           │  │                      │ │
│  │  ┌──────┐  ┌───────────┐ │  │                      │ │
│  │  │ Eden │  │ Survivor  │ │  │   Tenured (장기)     │ │
│  │  │      │  │  S0 │ S1  │ │  │                      │ │
│  │  └──────┘  └───────────┘ │  │                      │ │
│  │   (새 객체)   (임시 보관) │  │   (오래된 객체)       │ │
│  └──────────────────────────┘  └──────────────────────┘ │
│           ▲                              ▲               │
│           │ Minor GC                     │ Major GC      │
└───────────┼──────────────────────────────┼───────────────┘
            │                              │
       빠르고 빈번                      느리고 드물게

객체 생성 흐름:

public class HeapAllocationExample {
    public static void main(String[] args) {
        // 1. Eden 영역에 객체 생성
        User user1 = new User("Alice");

        // 2. Eden이 가득 차면 Minor GC 발생
        for (int i = 0; i < 1000000; i++) {
            User temp = new User("User" + i);
            // Eden → Survivor 0 이동
        }

        // 3. 여러 번 Minor GC 생존 → Old Generation 이동
        // user1 참조가 계속 유지 → Old로 Promotion
    }
}

Age-based Promotion:

Eden → S0 → S1 → S0 → ... (15번 반복) → Old Generation

객체의 Age:
- Minor GC 때마다 Age + 1
- -XX:MaxTenuringThreshold=15 (기본값)
- Age >= 15 → Old Generation 이동

JVM 힙 옵션:

# Heap 크기 설정
-Xms2g              # 초기 Heap 크기
-Xmx4g              # 최대 Heap 크기

# Young/Old Generation 비율
-XX:NewRatio=2      # Old:Young = 2:1
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1:1

# 예시: -Xmx4g -XX:NewRatio=2
# Heap 4GB → Young 1.33GB, Old 2.67GB
# Young 1.33GB → Eden 1.07GB, S0 133MB, S1 133MB

3.3 JVM Stack (스택 영역)

구조:

Thread 1 Stack          Thread 2 Stack
┌─────────────┐        ┌─────────────┐
│ Frame 3     │        │ Frame 2     │
├─────────────┤        ├─────────────┤
│ Frame 2     │        │ Frame 1     │
├─────────────┤        └─────────────┘
│ Frame 1     │
└─────────────┘

각 Frame 구조:
┌─────────────────────────┐
│   Local Variables       │  ← 지역 변수, 매개변수
├─────────────────────────┤
│   Operand Stack         │  ← 연산 중간 결과
├─────────────────────────┤
│   Frame Data            │  ← 메서드 정보, 리턴 주소
└─────────────────────────┘

예제:

public class StackExample {
    public static void main(String[] args) {  // Frame 1
        int x = 10;  // Local Variable
        int result = add(x, 20);  // Frame 2 생성
        System.out.println(result);
    }  // Frame 1 pop

    public static int add(int a, int b) {  // Frame 2
        int sum = a + b;  // Local Variable
        return sum;  // Frame 2 pop, 결과를 Frame 1의 Operand Stack으로
    }
}

Stack 메모리 흐름:

1. main() 호출 → Frame 1 push
   Local Variables: args, x=10, result=?

2. add(10, 20) 호출 → Frame 2 push
   Local Variables: a=10, b=20, sum=30
   Operand Stack: 30 (리턴값)

3. add() 종료 → Frame 2 pop
   result = 30 (Operand Stack에서 가져옴)

4. main() 종료 → Frame 1 pop

StackOverflowError:

public class StackOverflowExample {
    public static void main(String[] args) {
        recursiveMethod(0);
    }

    public static void recursiveMethod(int depth) {
        System.out.println("Depth: " + depth);
        recursiveMethod(depth + 1);  // 무한 재귀
        // StackOverflowError 발생!
    }
}

// JVM 옵션으로 Stack 크기 조정
// -Xss1m (기본값: 1MB)

3.4 PC Register (Program Counter)

역할:

  • 현재 실행 중인 JVM 명령어 주소 저장
  • 각 스레드마다 독립적으로 존재
public class PCRegisterExample {
    public static void main(String[] args) {
        int a = 10;     // PC: 라인 3
        int b = 20;     // PC: 라인 4
        int c = a + b;  // PC: 라인 5
        // PC Register는 다음에 실행할 명령어 주소를 가리킴
    }
}

Bytecode로 보는 PC:

Bytecode:
0: bipush 10      ← PC = 0
2: istore_1       ← PC = 2
3: bipush 20      ← PC = 3
5: istore_2       ← PC = 5
6: iload_1        ← PC = 6
7: iload_2        ← PC = 7
8: iadd           ← PC = 8
9: istore_3       ← PC = 9

3.5 Native Method Stack

JNI (Java Native Interface) 호출 시 사용:

public class NativeMethodExample {
    // Native 메서드 선언
    public native void nativeMethod();

    static {
        // C/C++ 라이브러리 로드
        System.loadLibrary("native-lib");
    }

    public static void main(String[] args) {
        new NativeMethodExample().nativeMethod();
        // Native Method Stack 사용
    }
}


📚 다음 편: 준비 중입니다.


👈 이전 편: JVM 메모리 (Part 1: 구조와 기초)