🎯1주차 Unit 4.1 — JVM 런타임 데이터 영역

Psj·2026년 5월 7일

F-lab

목록 보기
35/240

🎯 Unit 4.1 — JVM 런타임 데이터 영역 ★★★

F-lab Java 1주차 / Phase 4 / Unit 4.1 본격 학습 자료
9-섹션 마스터 프롬프트 형식으로 깊이 파헤친다.

선수 지식: Phase 1, 2 (OOP, 클래스/객체)
다음 Unit: 4.2 — Pass by Value (자바의 진실)

이 Unit의 의미: Phase 4 의 시작 — 추상의 세계에서 물리의 세계로.
면접에서 거의 100% 출제 되는 영역. 4-5주차 동시성/GC, 11-12주차 JPA 영속성 컨텍스트의 토대.


🌍 1. 세상 속 비유

JVM 메모리 = "잘 정리된 도서관"

큰 도서관을 상상해보세요. 도서관에는 여러 공간 이 있습니다:

1층 — 카운터 (PC Register):

  • 사서가 지금 어떤 책을 처리하고 있는지 표시
  • 매 순간 한 가지 일만

2층 — 작업 책상 (Stack):

  • 사서가 작업 중인 메모, 임시 자료 보관
  • 작업 끝나면 정리
  • 다른 사서는 다른 책상

3층 — 장서 보관실 (Heap):

  • 모든 책의 본체 저장
  • 가장 큰 공간
  • 여러 사서가 공유

지하 — 카탈로그실 (Method Area):

  • 책의 목록, 분류 정보, 저자 정보
  • 모든 사서가 참조
  • 한 번 만들면 거의 안 바뀜

JVM 메모리도 비슷한 구조. 각 공간이 다른 목적.


더 직관적인 비유 — "사무실"

당신이 회사에서 일한다고 상상해보세요:

공유 캐비닛 (Heap):

  • 모든 직원이 사용하는 공동 자료
  • 보고서 원본, 회사 문서들
  • 누가 사용 중인지 추적 필요 (GC)

개인 책상 (Stack):

  • 당신만의 작업 공간
  • 임시 메모, 계산기, 연필
  • 다른 직원과 공유 X
  • 일이 끝나면 정리

회사 매뉴얼 (Method Area):

  • 회사 규정, 직무 설명서
  • 모든 직원이 참조
  • 잘 안 바뀜

지금 하는 작업 표시 (PC Register):

  • 책상 위 "현재 작업: 보고서 작성" 표지

→ 자바 프로그램이 실행될 때 JVM 안에서 정확히 이런 공간들이 만들어집니다.


핵심 한 문장

"JVM은 자바 프로그램 실행을 위해 메모리를 5가지 영역으로 나누어 관리한다."

5가지 영역의 핵심 차이:

영역공유 여부보관 대상
Method Area모든 스레드 공유클래스 정보, static
Heap모든 스레드 공유모든 객체
Stack스레드별메서드 호출, 지역변수
PC Register스레드별현재 명령 위치
Native Method Stack스레드별네이티브 메서드

🔥 2. 탄생 배경

"메모리를 잘 관리하는 게 왜 중요한가?"

프로그램은 결국 메모리에서 실행 됩니다. 메모리 관리가 잘못되면:

  • 프로그램 충돌 (Crash)
  • 메모리 누수 (Memory Leak)
  • 보안 취약점

C/C++ 의 시대 — 개발자가 직접 관리

C에서는 메모리를 개발자가 직접 관리:

// C 코드
int* arr = malloc(sizeof(int) * 100);  // 메모리 할당
// ... 사용 ...
free(arr);  // 명시적으로 해제 ❗
// 해제 안 하면 → 메모리 누수
// 두 번 해제하면 → 충돌

문제:

  • 개발자 실수로 메모리 누수, 댕글링 포인터
  • 보안 취약점 (Buffer Overflow)
  • 디버깅 지옥

Java 의 등장 — "메모리 자동 관리"

Java(1995) 의 핵심 약속:

"메모리 관리는 JVM이 알아서"

자바 개발자는:

  • new 로 객체 생성만
  • 해제는 GC가 자동
  • 메모리 누수 위험 ↓
  • 보안 ↑

이를 위해 JVM은 명확한 메모리 영역 구분 이 필요했습니다.


JVM 메모리 모델의 표준화

JVM Specification (1995~) 에서 정의:

  • 모든 자바 구현체가 따라야 할 메모리 영역 표준
  • HotSpot, OpenJDK, GraalVM 등 모두 이 모델 따름

5가지 영역:
1. Method Area (모든 스레드 공유)
2. Heap (모든 스레드 공유)
3. Stack (스레드별)
4. PC Register (스레드별)
5. Native Method Stack (스레드별)

이게 면접 단골 "JVM 메모리 구조".


왜 영역을 나누나?

영역을 나누는 이유 ⭐ :

1. 생명주기 관리

  • 어떤 데이터는 오래 살고 (클래스 정보, 일부 객체)
  • 어떤 데이터는 짧게 살고 (지역변수, 임시 객체)
  • → 영역마다 다른 GC 전략

2. 동시성

  • 스레드별 영역 (Stack, PC Register) → 동기화 불필요
  • 공유 영역 (Heap, Method Area) → 동기화 필요

3. 성능 최적화

  • Stack은 매우 빠름 (LIFO, 단순)
  • Heap은 GC 필요하지만 유연

4. 보안

  • 영역 분리로 메모리 침입 방어

핵심 통찰

"JVM 메모리 구조를 모르면 자바를 진짜 이해 못한 것이다."

Pass by Value (Unit 4.2), 동시성 (4주차), GC (5주차), JPA 영속성 컨텍스트 (12주차) 등 — 모든 자바의 깊은 주제가 이 메모리 구조 위에 있다.

면접에서도 거의 100% 묻는 영역. 답할 수 있느냐 없느냐가 시니어 차별화 지점.


💣 3. 없으면 생기는 문제

"메모리 구조를 모르고 자바를 쓰면?"

깊이 이해 없이 자바를 쓰면 다양한 문제에서 막힙니다.


시나리오 1: NullPointerException 의 진짜 원인

public class FareService {
    private List<Fare> fares;
    
    public int countFares() {
        return fares.size();  // 💥 NullPointerException
    }
}

메모리 구조 모르면:

  • "왜 null인지 모르겠어요"
  • 그냥 if 체크로 회피

메모리 구조 알면:

  • fares참조 변수 (Stack 또는 Heap의 객체 안)
  • 참조가 가리키는 Heap 의 List 객체가 없음 = null
  • 초기화 안 함 → null
  • 근본 원인 파악

시나리오 2: ArrayList의 의외의 동작

public void process(List<Integer> list) {
    list.add(100);  // 호출자의 list에도 영향?
}

List<Integer> myList = new ArrayList<>();
process(myList);
System.out.println(myList);  // [100] ⚠️ 변경됨!

메모리 구조 모르면:

  • "값이 복사된다고 했는데 왜 변경되지?"

메모리 구조 알면:

  • myList 는 Stack의 참조 변수
  • Heap에 ArrayList 객체 1개
  • process(myList)참조의 값 이 복사되어 전달
  • 메서드 내부의 list 와 외부의 myList같은 Heap 객체 가리킴
  • → 한쪽에서 변경 시 양쪽에서 보임

Pass by Value의 진실 (Unit 4.2 미리보기).


시나리오 3: OutOfMemoryError

List<byte[]> leaks = new ArrayList<>();
while (true) {
    leaks.add(new byte<[1024 * 1024]);  // 1MB씩 추가
}
// 💥 OutOfMemoryError: Java heap space

메모리 구조 모르면:

  • "메모리 부족하대요. 어떻게 늘려요?"

메모리 구조 알면:

  • Heap 영역의 최대 크기 초과
  • leaks 가 GC 대상 안 됨 (참조 살아있음)
  • -Xmx 옵션으로 Heap 증가 가능
  • 그러나 근본 해결 = 불필요한 참조 제거

→ 5주차 GC 학습의 토대.


시나리오 4: StackOverflowError

public void recursion() {
    recursion();  // 무한 재귀
}
recursion();  // 💥 StackOverflowError

메모리 구조 모르면:

  • "왜 OutOfMemoryError가 아닌 StackOverflowError?"

메모리 구조 알면:

  • 메서드 호출 시마다 Stack에 프레임 추가
  • Stack 최대 크기 초과 → StackOverflowError
  • Stack은 Heap과 별도 영역
  • → 스레드별로 다름

시나리오 5: 동시성 버그

public class Counter {
    private int count = 0;
    
    public void increment() {
        count++;  // 멀티스레드 환경에서 문제
    }
}

메모리 구조 모르면:

  • "왜 가끔 결과가 이상하지?"
  • "synchronized 붙이면 되겠지"

메모리 구조 알면:

  • countHeap 의 객체에 속한 인스턴스 변수
  • 여러 스레드가 같은 객체 접근 가능 (Heap 공유)
  • 각 스레드의 Stack에서 임시 변수로 처리 → 충돌
  • JVM Memory Model, Happens-before 등 깊은 주제

→ 4주차 동시성 학습의 토대.


시나리오 6: 면접 탈락

"JVM 메모리 구조를 설명해주세요"

대답 못함:

  • "Heap이랑 Stack이 있는 거 같은데..."
  • 시니어 자격 탈락

잘 답함:

  • "JVM은 5가지 영역으로 나뉩니다. Method Area, Heap, Stack, PC Register, Native Method Stack..."
  • 각 영역의 역할, 공유 여부, 저장 대상 설명
  • → 시니어 후보로 인식

메모리 구조의 중요성

지금 학습 안 하면:

  • Pass by Value (4.2) 이해 X
  • 동시성 (4주차) 이해 X
  • GC (5주차) 이해 X
  • JPA 영속성 컨텍스트 (12주차) 이해 X
  • 면접에서 무너짐

이 Unit이 향후 모든 자바 학습의 토대.


✅ 4. 해결책 — JVM 메모리 영역 5가지

전체 구조도 ⭐

┌─────────────────────────────────────────────┐
│              JVM Runtime                     │
│                                              │
│  ┌─────────────────┐  ┌─────────────────┐  │
│  │  Method Area    │  │      Heap       │  │
│  │  (Class Info)   │  │  (Objects)      │  │
│  │                 │  │                 │  │
│  │  - 클래스 메타   │  │  - 모든 객체    │  │
│  │  - static 변수  │  │  - 인스턴스 변수│  │
│  │  - Constant Pool│  │  - 배열         │  │
│  └─────────────────┘  └─────────────────┘  │
│         (모든 스레드 공유)                   │
│                                              │
│  ┌──────────────┬──────────────┬─────────┐ │
│  │ Thread 1     │ Thread 2     │ ...      │ │
│  │              │              │          │ │
│  │ - PC Register│ - PC Register│          │ │
│  │ - Stack      │ - Stack      │          │ │
│  │ - Native     │ - Native     │          │ │
│  │   Method Stk │   Method Stk │          │ │
│  └──────────────┴──────────────┴─────────┘ │
│         (스레드별 분리)                      │
└─────────────────────────────────────────────┘

1. Method Area ⭐ (Metaspace, Java 8+)

역할: 클래스 정보 저장

저장 내용:

  • 클래스의 메타데이터 (이름, 부모, 인터페이스)
  • 메서드 코드 (바이트코드)
  • 필드 정보 (이름, 타입)
  • static 변수 ⭐
  • 상수 풀 (Constant Pool) — 리터럴 등

특징:

  • 모든 스레드 공유
  • JVM 시작 시 생성
  • 한 번 로드되면 거의 안 바뀜
  • Java 7까지: PermGen (영구 영역)
  • Java 8+: Metaspace (네이티브 메모리)

예시:

public class Customer {
    public static int count = 0;  // Method Area에
    private String name;          // 객체 마다 (Heap에)
    
    public void hello() { ... }   // 메서드 코드 → Method Area
}

2. Heap ⭐⭐ (가장 중요)

역할: 모든 객체 저장

저장 내용:

  • new 로 만든 모든 객체
  • 인스턴스 변수
  • 배열

특징:

  • 모든 스레드 공유
  • 가장 큰 영역
  • GC 의 주 대상 (5주차 학습)
  • -Xmx, -Xms 옵션으로 크기 설정

Heap 의 내부 구조 (HotSpot JVM):

[Heap]
  ├── Young Generation
  │   ├── Eden
  │   ├── Survivor 0
  │   └── Survivor 1
  └── Old Generation

→ 5주차 GC에서 자세히.

예시:

Customer c = new Customer();
//             ↑ Heap에 Customer 인스턴스 생성
//             c는 Stack에 있는 참조 변수

3. Stack ⭐⭐ (메서드 호출의 무대)

역할: 메서드 호출 정보, 지역변수

저장 내용:

  • Stack Frame (메서드 호출마다 1개)
  • 지역변수
  • 메서드 매개변수
  • 리턴 주소

특징:

  • 스레드별로 따로
  • LIFO (Last In, First Out)
  • 매우 빠름
  • 메서드 종료 → 프레임 자동 제거

Stack Frame 구조:

[Stack Frame]
  - Local Variable Array (지역변수)
  - Operand Stack (계산용)
  - Frame Data (리턴 주소 등)

예시:

public void method1() {        // Stack Frame 1
    int x = 10;                // Frame 1의 지역변수
    method2();                  // 새 Frame 2 추가
    int y = 20;                // 다시 Frame 1
}

public void method2() {        // Stack Frame 2
    int a = 100;               // Frame 2의 지역변수
    // method2 종료 → Frame 2 제거
}

4. PC Register

역할: 현재 실행 중인 명령의 주소

특징:

  • 스레드별로 따로
  • 매우 작음 (1개 주소만 저장)
  • "지금 어느 명령 실행 중?" 추적

예시:

public void method() {
    int x = 1;   // PC: line 1
    int y = 2;   // PC: line 2
    int z = x + y; // PC: line 3
}

→ 스레드 전환 시 어디까지 했는지 기억하는 용도.


5. Native Method Stack

역할: 네이티브 메서드 (C/C++) 호출 정보

저장 내용:

  • 자바 외 언어로 작성된 메서드의 호출 정보

특징:

  • 스레드별
  • JNI (Java Native Interface) 호출 시 사용

예시:

public class System {
    public static native long currentTimeMillis();
    //                ↑ 네이티브 메서드
}

System.currentTimeMillis();
// → Java Stack에서 호출
// → Native Method Stack으로 전환
// → C 코드 실행

→ 일반 개발자가 직접 다룰 일 적음.


영역별 핵심 비교 표 ⭐

영역공유 여부저장크기생성 시점
Method Area모든 스레드클래스 정보, staticJVM 시작
Heap모든 스레드객체, 배열가장 큼JVM 시작
Stack스레드별메서드 프레임작음스레드 시작
PC Register스레드별현재 명령 위치매우 작음스레드 시작
Native Method Stack스레드별네이티브 호출작음스레드 시작

변수의 종류와 저장 위치 ⭐⭐ (자기 점검 Q2)

public class Customer {
    private static int totalCount = 0;  // ① static
    private String name;                 // ② 인스턴스 변수
    
    public void hello() {
        int localVar = 10;               // ③ 지역변수
        Customer other = new Customer(); // ④ 지역 참조 변수
    }
}

저장 위치:

  • totalCount (static) → Method Area
  • name (인스턴스) → Heap (객체 안)
  • localVar (지역, 기본형) → Stack
  • other (지역 참조) → Stack (참조), Heap (객체)

→ 면접에서 자주 묻는 질문.


🏗️ 5. 내부 동작 원리

클래스 로딩 과정 ⭐

public class Hello {
    public static void main(String[] args) {
        Customer c = new Customer();
    }
}

JVM 시작 시:

1. ClassLoader가 .class 파일 읽음
        ↓
2. 클래스 정보를 Method Area에 저장
   - Customer 클래스 메타데이터
   - 메서드 바이트코드
   - static 변수 영역 할당
        ↓
3. main 메서드 실행 시작
        ↓
4. main 메서드용 Stack Frame 생성
   - args 매개변수
   - c 지역변수 자리 (아직 비어있음)
        ↓
5. new Customer() 실행
   - Heap에 Customer 인스턴스 생성
   - 인스턴스 변수 초기화
        ↓
6. c에 인스턴스 참조 저장 (Stack의 c 변수에)

결과 메모리 상태:

[Method Area]
  Customer 클래스 정보
  
[Heap]
  Customer 인스턴스 (인스턴스 변수들)
       ↑
       │ 참조
[Stack — main 스레드]
  main Frame
    - args
    - c (참조) ─────┘

메서드 호출 시 메모리 변화 ⭐

public class Calculator {
    public int add(int a, int b) {
        int sum = a + b;
        return sum;
    }
}

Calculator calc = new Calculator();
int result = calc.add(3, 5);

Step 1 — 호출 직전:

[Heap]
  Calculator 인스턴스
       ↑
[Stack — main]
  main Frame
    - calc (참조)
    - result (아직 미할당)

Step 2 — calc.add(3, 5) 호출:

[Stack — main]
  main Frame
    - calc
    - result
  ┌──────────────┐
  │  add Frame   │  ← 새 Frame 추가
  │  - this (참조)│  ← 자동으로 Calculator 인스턴스 참조
  │  - a = 3     │  ← 매개변수
  │  - b = 5     │
  │  - sum = ?   │  ← 지역변수
  └──────────────┘

Step 3 — sum = a + b 실행:

  add Frame
    - this
    - a = 3
    - b = 5
    - sum = 8     ← 계산 결과

Step 4 — return:

[Stack — main]
  main Frame
    - calc
    - result = 8  ← 반환값 받음
                   add Frame은 제거됨

Stack Frame이 메서드 호출의 핵심.


멀티스레드 시 메모리 ⭐

public class Shared {
    public int counter = 0;  // 공유 데이터
}

public class MyThread extends Thread {
    private Shared shared;
    private int localValue;  // 스레드별 데이터
    
    public void run() {
        shared.counter++;
        localValue = 100;
    }
}

Shared shared = new Shared();

MyThread t1 = new MyThread();
t1.shared = shared;
t1.start();

MyThread t2 = new MyThread();
t2.shared = shared;
t2.start();

메모리 상태:

[Method Area]
  Shared 클래스 정보, MyThread 클래스 정보
  
[Heap]  (모든 스레드 공유)
  Shared 인스턴스 (counter)
       ↑       ↑
       │       │
[Stack t1] [Stack t2]  (스레드별 분리)
  - shared    - shared
    참조        참조
  - localValue - localValue
    (각자 독립)  (각자 독립)

핵심:

  • shared.counterHeap 에 1개 → 두 스레드가 동시 변경 시 race condition
  • localValue각 스레드의 Stack 에 따로 → 안전

4주차 동시성 학습의 토대.


Java 8 의 변화 — PermGen → Metaspace ⭐

Java 7 까지:

[JVM Heap]
  ├── Young Generation
  ├── Old Generation
  └── PermGen (영구 영역)  ← Method Area의 일부
       - 클래스 정보
       - static 변수
       - 상수 풀

문제:

  • PermGen 크기 고정 → OutOfMemoryError: PermGen space 자주 발생
  • 동적 클래스 로딩 (Spring, JSP 등) 시 부족

Java 8+:

[JVM Heap]
  ├── Young Generation
  └── Old Generation

[Native Memory]
  └── Metaspace  ← Method Area
       - 동적 크기 조정 (OS 메모리 한도까지)
       - 더 안정적

효과:

  • OutOfMemoryError: PermGen space 사라짐
  • 대신 OutOfMemoryError: Metaspace 가능 (드문 일)

면접 빈출 질문: "Java 8 이후 메모리 변화?"


String Constant Pool ⭐

문자열은 특별 취급:

String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");

s1 == s2;  // true (같은 String Pool 참조)
s1 == s3;  // false (s3는 Heap에 새 객체)
s1.equals(s3);  // true (값 비교)

메모리 상태:

[Method Area / Heap의 특별 영역]
  String Constant Pool
    "hello" ← 1개만
       ↑  ↑
       │  │
[Heap]
  새 String 객체 (s3가 가리킴)
       ↑
       │
[Stack]
  s1 ───┘
  s2 ───┘
  s3 ───┘ (새 객체 가리킴)

6주차 String 학습 으로 연결.


💻 6. 실전 코드 예시

예시 1: 메모리 위치 추적

public class FareSystem {
    
    // Method Area에 저장
    private static int totalFareCount = 0;
    private static final String VERSION = "1.0";
    
    // 인스턴스 변수 — 객체와 함께 Heap에
    private Long id;
    private int amount;
    private FareStatus status;
    
    // 메서드 코드 — Method Area에
    public void changeAmount(int newAmount) {
        // 매개변수 newAmount, 지역변수 → Stack
        int oldAmount = this.amount;  // 지역변수
        
        if (newAmount < 0) {
            throw new IllegalArgumentException();
        }
        this.amount = newAmount;  // Heap의 인스턴스 변수 변경
        totalFareCount++;          // Method Area의 static 변경
    }
}

메모리 추적:

  • totalFareCount → Method Area
  • VERSION → Method Area + String Pool
  • id, amount, status → Heap (각 인스턴스마다)
  • changeAmount 메서드 코드 → Method Area
  • newAmount, oldAmount → Stack (호출 시)

예시 2: 객체 생성과 메모리

public class FareDemo {
    public static void main(String[] args) {
        // Step 1: Customer 인스턴스 생성
        Customer alice = new Customer("Alice");
        // alice → Stack
        // Customer 인스턴스 → Heap
        
        // Step 2: Fare 인스턴스 생성
        Fare fare = new Fare(50000, alice);
        // fare → Stack
        // Fare 인스턴스 → Heap (customer 필드는 alice 참조)
        
        // Step 3: 같은 alice 참조
        Fare fare2 = new Fare(80000, alice);
        // alice 객체는 Heap에 1개, 두 Fare가 공유
        
        // Step 4: alice 변경
        alice.setName("Alice Smith");
        // → Heap의 Customer 객체 변경
        // → fare.customer.getName() == "Alice Smith"
        // → fare2.customer.getName() == "Alice Smith"
    }
}

메모리 그림:

[Heap]
  Customer ("Alice Smith")  ← 1개
       ↑           ↑
       │           │
  Fare(50000)  Fare(80000)
       ↑           ↑
       │           │
[Stack — main]
  alice ─────────────┐
  fare ──────────────┤
  fare2 ─────────────┘

예시 3: Stack Overflow 예시

public class StackDemo {
    public static int factorial(int n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);  // 재귀
    }
    
    public static void main(String[] args) {
        System.out.println(factorial(10));     // OK
        System.out.println(factorial(100000)); // 💥 StackOverflowError
    }
}

왜 StackOverflow?:

  • 매 호출마다 Stack Frame 추가
  • Stack 크기 한계 (보통 512KB ~ 1MB) 초과
  • → 동적 계획법 등으로 해결

해결:

// 반복문으로
public static int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

→ Stack을 거의 안 씀.


예시 4: Heap OutOfMemory 예시

public class HeapDemo {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            list.add(new byte[1024 * 1024]);  // 1MB씩 추가
            // 💥 OutOfMemoryError: Java heap space
        }
    }
}

해결:

# JVM 옵션으로 Heap 크기 증가
java -Xmx4g HeapDemo  # 4GB로 증가

근본 해결: 불필요한 참조 제거 (5주차 GC).


예시 5: 멀티스레드 메모리

public class CounterTest {
    private int count = 0;  // Heap의 인스턴스 변수
    
    public void increment() {
        count++;  // 멀티스레드에 위험
    }
    
    public static void main(String[] args) throws InterruptedException {
        CounterTest counter = new CounterTest();
        
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) counter.increment();
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) counter.increment();
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println(counter.count);  // 20000? — 보통 적게 나옴
    }
}

왜 적게?:

  • count 는 Heap 의 객체에 속함 (공유)
  • count++ 는 read → modify → write 3 단계
  • 두 스레드가 동시 read 시 같은 값 → 한 번 증가 누락
  • race condition

해결:

private AtomicInteger count = new AtomicInteger(0);
// 또는 synchronized

→ 4주차 동시성에서 본격.


⚠️ 7. 주의사항 & 흔한 실수

실수 1: "Stack과 Heap" 만 안다

Q: "JVM 메모리 구조?"
A: "Stack과 Heap이요"

5가지 영역 중 2개만. 면접에서 깊이 부족 노출.

올바른 답: Method Area, Heap, Stack, PC Register, Native Method Stack — 5가지 + 각각의 역할.


실수 2: static 변수가 Heap이라고 함

Q: "static 변수는 어디에?"
A: "Heap이요" ❌

정답: Method Area (Java 8+ Metaspace).

이유:

  • static 은 클래스에 속함
  • 클래스 정보가 Method Area에 저장
  • 인스턴스 만들지 않아도 존재
Customer.totalCount;  // 인스턴스 없이 접근
                      // → Method Area에 있어야 가능

실수 3: 모든 객체가 Heap이라고 단순화

Q: "객체는 어디에 저장?"
A: "Heap" ❌ (틀린 건 아니지만 부정확)

보다 정확한 답:

  • 인스턴스 객체 → Heap
  • 객체의 참조 변수 → Stack (지역변수면) 또는 Heap (필드면)
  • 클래스 정보 자체 → Method Area

실수 4: PermGen 과 Metaspace 혼동

Q: "Java 8 이후 PermGen은?"
A: "PermGen이 더 커졌어요" ❌

정답: PermGen이 사라지고 Metaspace로 교체.

  • Java 7까지: PermGen (Heap 일부, 고정 크기)
  • Java 8+: Metaspace (Native Memory, 동적 크기)

실수 5: Stack 크기를 Heap 처럼 큰 줄 안다

public void recursion(int depth) {
    // 1만 번 재귀
    if (depth < 10000) recursion(depth + 1);
}
recursion(0);  // 💥 StackOverflowError 가능

→ Stack은 수백 KB ~ 수 MB. Heap (수 GB) 와 매우 다름.


실수 6: 지역변수도 GC 대상이라고 함

Q: "지역변수가 GC 대상?"
A: "네" ❌

정답: 지역변수는 메서드 종료 시 자동 제거 (Stack Frame 제거). GC 대상 아님.

public void method() {
    int x = 10;  // Stack 에
    String s = "hello";  // s는 Stack의 참조, "hello"는 별도
}
// 메서드 종료 → x, s 자동 제거

GC 대상은 Heap 의 객체 만.


실수 7: "JVM 옵션은 Heap만 설정 가능"

흔한 오해:

java -Xmx4g  # Heap만 설정?

정답: 다양한 영역 설정 가능.

JVM 옵션 ⭐ :

-Xms<size>      # Heap 초기 크기
-Xmx<size>      # Heap 최대 크기
-Xss<size>      # Stack 크기 (스레드별)
-XX:MetaspaceSize=<size>          # Metaspace 초기
-XX:MaxMetaspaceSize=<size>       # Metaspace 최대
-XX:+HeapDumpOnOutOfMemoryError   # OOM 시 덤프

🔗 8. 연관 개념 맵

Phase 4 (JVM 메모리) 내 흐름

[Unit 4.1: JVM 런타임 데이터 영역]  ← 지금 여기
        ↓
[Unit 4.2: Pass by Value (자바의 진실)]
   메모리 구조 위에서 값/참조 전달 이해

Phase 4의 의미:

  • Phase 1-3: "어떻게 설계할까" (논리)
  • Phase 4: "어떻게 동작할까" (물리)

Phase 2와의 통합

이전 학습 다시 보기:

Phase 2 학습메모리 관점
Unit 2.1 (메서드)Stack Frame
Unit 2.3 (상속)Heap의 객체에 부모+자식 필드 함께
Unit 2.4 (다형성)VMT가 Method Area에
Unit 2.6 (Inner)Outer 인스턴스 참조가 Heap에

이전 학습이 메모리 구조 위에서 이해됨.


미래 주차와의 연결 ⭐

Phase 5 (GC):

  • Heap의 객체 수거 메커니즘
  • Young/Old Generation
  • Heap 이해 필수

Phase 6 (String, 컬렉션):

  • String Constant Pool
  • ArrayList vs LinkedList의 메모리 차이

Phase 7 (I/O):

  • 메모리에서 파일/네트워크로

4주차 (동시성):

  • 스레드별 Stack vs 공유 Heap
  • Memory Model, Happens-before
  • volatile, synchronized

5주차 (Spring):

  • Bean 들이 Heap에 어떻게 저장되나
  • Singleton 의 의미

11-12주차 (JPA):

  • 영속성 컨텍스트가 Heap에
  • 1차 캐시 = Heap의 Map

17주차 (MSA):

  • 각 서비스가 별도 JVM
  • 별도 Heap, Method Area

모든 자바 학습의 토대.


Java 메모리 진화

[Java 1.0 ~ 1.4]
  - 단순 메모리 모델
  - Stop-the-world GC
  
[Java 5]
  - JVM Memory Model 정립
  - volatile, synchronized 의미 명확화
  
[Java 7]
  - G1 GC 도입 (Heap 영역화)
  
[Java 8] ⭐
  - PermGen → Metaspace
  - 람다, Stream
  
[Java 11]
  - ZGC (대용량 Heap)
  
[Java 21+]
  - Generational ZGC
  - Project Loom (가상 스레드)

메모리 모델은 끝없이 진화.


면접 단골 질문 매핑

질문이 Unit에서의 답
"JVM 메모리 구조?"5가지 영역 + 각 역할
"static 변수는 어디?"Method Area
"Stack과 Heap 차이?"스레드별/공유, 작음/큼, 자동 정리/GC
"Java 8 메모리 변화?"PermGen → Metaspace
"OutOfMemoryError 종류?"Heap, Metaspace, Stack
"Stack Overflow vs OOM?"Stack vs Heap

📝 9. 핵심 요약 — 3줄 정리

1️⃣ JVM 메모리는 5가지 영역 — Method Area, Heap, Stack, PC Register, Native Method Stack.

공유 영역 (Method Area, Heap)스레드별 영역 (Stack, PC Register, Native Method Stack) 으로 나뉜다. Method Area 는 클래스 정보와 static, Heap 은 모든 객체, Stack 은 메서드 호출과 지역변수. 이 구분이 동시성/GC/성능 최적화의 토대.

2️⃣ 변수는 종류에 따라 저장 위치가 다르다.

static 변수 → Method Area, 인스턴스 변수 → Heap (객체 안), 지역변수 (기본형) → Stack, 지역 참조 변수 → Stack (참조), Heap (객체). 면접에서 자주 묻는 영역. Pass by Value (Unit 4.2) 의 핵심 토대.

3️⃣ Java 8 이후 PermGen이 Metaspace 로 교체됐다.

Java 7 까지의 PermGen 은 Heap 안에 고정 크기로 있어 OutOfMemoryError: PermGen space 가 잦았다. Java 8+ 의 Metaspace 는 Native Memory 에서 동적 크기, 더 안정적. JVM 옵션 -Xms/-Xmx (Heap), -Xss (Stack), -XX:MaxMetaspaceSize (Metaspace) 로 각 영역 통제 가능.


🎓 학습 자기 점검

기본 이해

  • JVM 의 5가지 메모리 영역을 나열할 수 있다
  • 각 영역의 공유 여부 (스레드별/공유) 를 안다
  • static 변수, 인스턴스 변수, 지역변수의 저장 위치를 구별한다
  • Java 8 이후 PermGen → Metaspace 변화를 안다

실전 적용

  • ILIC 코드의 변수들이 어디에 저장되는지 추적할 수 있다
  • StackOverflowError 와 OutOfMemoryError 의 차이를 안다
  • JVM 옵션으로 메모리 크기를 조정할 수 있다
  • 멀티스레드 시 메모리 공유 위험을 인식한다

면접 대비 (3-5분 답변)

  • "JVM 메모리 구조?" 답변 가능
  • "static 변수는 어디에?" 답변 가능
  • "Java 8 이후 PermGen 변화?" 답변 가능
  • "Stack과 Heap의 결정적 차이?" 답변 가능

자기 점검 질문 답변

Q1: Member m = new Member() 에서 m은 어디에, 객체 본체는 어디에 저장되는가?

한 줄 답: mStack (또는 Heap, 위치에 따라), 객체 본체는 Heap.

상세 설명:

위 코드를 분해:

Member m = new Member();
//     ↑       ↑
//     변수    객체

m 의 저장 위치 — 컨텍스트에 따라:

// Case 1: 메서드 안의 지역변수
public void method() {
    Member m = new Member();
    // m은 Stack의 method Frame 안 ← Stack
}

// Case 2: 클래스의 인스턴스 변수
public class Service {
    private Member m;  // m은 Service 인스턴스 안 ← Heap
}

// Case 3: static 변수
public class Service {
    private static Member m;  // m은 Method Area
}

객체 본체 (new Member() 로 생성된 것):

  • 항상 Heap
  • new 키워드 = "Heap에 객체 생성"

메모리 그림 (Case 1):

[Stack — 메서드 Frame]
  m (참조) ─────┐
                │
[Heap]          │
  Member 인스턴스 ◄─┘
   - 인스턴스 변수들

m == 객체 본체 가 아님:

  • m참조 변수 (객체의 주소를 담음)
  • 객체 본체는 별도로 Heap에 존재
  • → Java의 객체 변수는 항상 참조 (C 의 포인터와 비슷)

Q2: static 변수와 인스턴스 변수의 저장 위치 차이는?

한 줄 답: static 변수 → Method Area, 인스턴스 변수 → Heap (객체 안).

상세 설명:

public class Customer {
    private static int totalCount = 0;  // static
    private String name;                 // 인스턴스
    
    public Customer(String name) {
        this.name = name;
        totalCount++;
    }
}

Customer c1 = new Customer("Alice");
Customer c2 = new Customer("Bob");

메모리 상태:

[Method Area]
  Customer 클래스 정보
    - totalCount: 2  ← 1개만 존재 ⭐
  
[Heap]
  c1 인스턴스         c2 인스턴스
    - name: "Alice"     - name: "Bob"
    (totalCount는 여기 없음)
       ↑                  ↑
       │                  │
[Stack]
  c1 ──┘                  │
  c2 ───────────────────┘

핵심 차이 ⭐ :

static 변수인스턴스 변수
개수클래스당 1개인스턴스마다 1개
저장 위치Method AreaHeap (객체 안)
수명클래스 로드 ~ JVM 종료객체 생성 ~ GC
접근클래스명.변수인스턴스.변수
GC일반적으로 X객체 GC 시 함께

왜 이렇게 다른가?:

  • static 은 클래스에 속함 → 인스턴스 없이 존재 가능
  • 인스턴스 변수는 객체에 속함 → 객체마다 따로 필요

실용적 함의:

public class Counter {
    private static int total = 0;     // 모든 인스턴스가 공유
    private int myCount = 0;           // 각 인스턴스만의 값
    
    public void count() {
        total++;
        myCount++;
    }
}

Counter c1 = new Counter();
Counter c2 = new Counter();

c1.count(); c1.count();  // total = 2, c1.myCount = 2
c2.count();              // total = 3, c2.myCount = 1

결과:

  • total공유 → 3
  • myCount각자 → c1: 2, c2: 1

→ static 의 본질이 메모리 위치에서 드러남.

멀티스레드 시 위험 ⚠️ :

  • static 변수는 모든 스레드 공유
  • 동기화 없이 변경 시 race condition
  • → 4주차 동시성에서 본격.

다음 Unit으로

  • Pass by Value (자바의 진실) 을 학습할 준비 완료
  • "자바는 정말 Pass by Value 인가?" 의 진실이 궁금하다
  • 메모리 구조 위에서 값/참조 전달을 이해할 준비 완료
profile
Software Developer

0개의 댓글