개발이 취미인 사람

[Java] JVM 메모리 구조 - Static, Heap, Stack 완벽 정리 본문

언어(Programming Language)/Java

[Java] JVM 메모리 구조 - Static, Heap, Stack 완벽 정리

RyanSin 2025. 11. 5. 04:53
반응형

개요

안녕하세요. 이번 시간에는 JVM 메모리 구조에 대해 알아보겠습니다.

Java를 공부하다 보면 "이 변수는 어디에 저장되는 거지?", "static이랑 일반 변수는 뭐가 다른 거야?"라는 의문이 생기곤 합니다.

오늘은 JVM의 메모리가 어떻게 구성되어 있고, 우리가 작성한 코드가 실제로 어디에 저장되는지 깊이 있게 다뤄보겠습니다.

JVM 메모리는 크게 Static 영역, Heap 영역, Stack 영역 3가지로 나뉩니다. 각 영역이 무엇을 저장하고 어떤 특징이 있는지 예제 코드와 함께 살펴보겠습니다!

개념

JVM 메모리란?

JVM(Java Virtual Machine)이 프로그램을 실행하기 위해 운영체제로부터 할당받은 메모리 영역입니다.

이 메모리는 크게 3가지 영역으로 구분됩니다:

  1. Static 영역 (Method Area / Metaspace)
  2. Heap 영역
  3. Stack 영역

각 영역은 저장하는 데이터의 종류와 생명주기가 다릅니다. 이제 하나씩 자세히 알아보겠습니다.

1. Static 영역 (Method Area)

개념

Static 영역은 클래스 정보static 변수가 저장되는 공간입니다.

이 영역은 모든 스레드가 공유하며, 프로그램이 시작될 때 생성되어 종료될 때까지 유지됩니다.

Java 버전별 구현

Java 7 이전: PermGen (Permanent Generation)

- 고정 크기의 메모리 영역
- 문제점: OutOfMemoryError: PermGen space 빈번히 발생
- 설정: -XX:PermSize=128m -XX:MaxPermSize=256m

 

Java 8 이후: Metaspace

- Native 메모리 사용 (동적 확장 가능)
- PermGen의 문제점 해결
- 설정: -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m

저장되는 것들

  1. 클래스 메타데이터: 클래스 구조 정보, 필드/메서드 정보
  2. Static 변수: static 키워드로 선언한 클래스 변수
  3. Runtime Constant Pool: 문자열 리터럴, 숫자 상수 등

예시 코드

 
java
public class Counter {
    // Static 영역에 저장
    static int count = 0;
    static String name = "Counter";
    
    static void increment() {
        count++;
    }
}

메모리 상태:

Static 영역 (Metaspace)
├─ Counter 클래스 메타데이터
├─ count (static 변수) = 0
├─ name (static 변수) → "Counter" 참조
└─ increment() 메서드 바이트코드

2. Heap 영역

개념

Heap 영역은 new 키워드로 생성한 모든 객체가 저장되는 공간입니다.

이 영역도 모든 스레드가 공유하며, **Garbage Collection(GC)**의 대상이 됩니다.

저장되는 것들

  1. 객체 인스턴스: new로 생성한 모든 객체
  2. 배열: int[], String[] 등 모든 배열
  3. 문자열 객체: "Hello" 같은 리터럴, new String("Hello")

Heap 내부 구조

Heap은 GC를 효율적으로 하기 위해 세대별로 나뉩니다:

Heap
├─ Young Generation (새로 생성된 객체)
│  ├─ Eden Space
│  ├─ Survivor 0
│  └─ Survivor 1
└─ Old Generation (오래 살아남은 객체)
   └─ Tenured Space

예시 코드

java
public class Person {
    String name;  // 인스턴스 변수
    int age;
    
    public static void main(String[] args) {
        // Heap에 Person 객체 생성
        Person p = new Person();
        p.name = "김철수";
        p.age = 30;
        
        // Heap에 배열 생성
        int[] arr = {1, 2, 3};
    }
}

메모리 상태:

Heap
├─ Person 객체
│  ├─ name → "김철수" (String 객체도 Heap에 있음)
│  └─ age = 30
└─ int[] 배열 {1, 2, 3}

Stack (main 스레드)
├─ p → Heap의 Person 객체 주소
└─ arr → Heap의 배열 주소

중요한 점은 Stack에는 주소만 저장되고, 실제 객체는 Heap에 저장된다는 것입니다!

JVM 옵션

bash
-Xms512m   # 초기 Heap 크기
-Xmx2g     # 최대 Heap 크기

3. Stack 영역

개념

Stack 영역은 메서드 호출지역 변수를 저장하는 공간입니다.

중요한 특징은 각 스레드마다 독립적으로 생성된다는 점입니다. 그래서 Thread-Safe합니다!

저장되는 것들

  1. 메서드 호출 정보 (Stack Frame)
  2. 지역 변수
    • Primitive 타입: 값 자체 저장 (int, boolean 등)
    • Reference 타입: 주소만 저장 (실제 객체는 Heap)
  3. 메서드 파라미터
  4. 중간 연산 결과

Stack Frame 구조

Stack (Thread 1)
│
├─ main() Frame
│  ├─ Local Variables
│  │  ├─ int x = 10       ← 값 10이 직접 저장
│  │  └─ Person p         ← Heap 주소만 저장
│  └─ Operand Stack
│
└─ methodA() Frame
   └─ Local Variables

예시 코드

 
 
java
public class StackExample {
    public static void main(String[] args) {
        int x = 10;              // Stack: 값 10 직접 저장
        String str = "Hello";    // Stack: 주소만 저장
        
        methodA(x);              // Stack에 methodA Frame 생성
    }
    
    static void methodA(int param) {
        int y = 20;              // Stack: methodA Frame 안에 저장
        System.out.println(param + y);
    }  // methodA 종료 → Frame 제거
}

실행 순서별 메모리 상태

[1] main 시작

Stack (Thread 1)
└─ main() Frame
   ├─ x = 10
   └─ str → Heap의 "Hello"

[2] methodA 호출

Stack (Thread 1)
├─ main() Frame
│  ├─ x = 10
│  └─ str → Heap
└─ methodA() Frame
   ├─ param = 10 (복사된 값)
   └─ y = 20

[3] methodA 종료

Stack (Thread 1)
└─ main() Frame
   ├─ x = 10
   └─ str → Heap
   (methodA Frame 제거됨)

보시다시피 Stack은 LIFO(Last In First Out) 구조로 동작합니다!

StackOverflowError

Stack 크기를 초과하면 발생하는 에러입니다. 주로 무한 재귀 호출 때문에 발생합니다.

java
void infiniteRecursion() {
    infiniteRecursion();  // Stack Frame이 계속 쌓임
}  // StackOverflowError 발생!

3대 영역 비교표

이제 3가지 영역의 차이점을 한눈에 정리해보겠습니다.

영역공유 여부저장 대상GC 대상생명주기JVM 옵션

Static 모든 스레드 공유 클래스 정보, static 변수 클래스 로딩~언로딩 -XX:MetaspaceSize
Heap 모든 스레드 공유 객체, 배열 객체 생성~GC -Xms, -Xmx
Stack 스레드별 독립 지역 변수, 메서드 호출 메서드 호출~종료 -Xss

실전 예제 코드

백문일이 불여일타! 이제 실제 코드로 모든 영역을 한번에 확인해보겠습니다

 
java
public class MemoryExample {
    // Static 영역에 저장
    static int staticCount = 0;
    static String staticName = "Global";
    
    // 인스턴스 변수 (객체 생성 시 Heap에 저장)
    int instanceCount;
    String instanceName;
    
    public static void main(String[] args) {
        // Stack: x, y, z (값 직접 저장)
        int x = 10;
        int y = 20;
        int z = x + y;
        
        // Stack: obj1, obj2 (주소만 저장)
        // Heap: MemoryExample 객체 2개 생성
        MemoryExample obj1 = new MemoryExample();
        MemoryExample obj2 = new MemoryExample();
        
        obj1.instanceCount = 1;  // Heap의 obj1 안에 저장
        obj2.instanceCount = 2;  // Heap의 obj2 안에 저장
        
        staticCount = 100;       // Static 영역 (모든 객체 공유)
        
        methodA();               // Stack에 Frame 추가
    }
    
    static void methodA() {
        // Stack: methodA Frame 안에 local 저장
        int local = 5;
        System.out.println(local);
    }  // Frame 제거
}

실행 시 메모리 상태

 
 
Static 영역 (Metaspace)
├─ MemoryExample 클래스 메타데이터
├─ staticCount = 100
└─ staticName → "Global" (Heap)

Heap
├─ MemoryExample 객체 (obj1)
│  ├─ instanceCount = 1
│  └─ instanceName = null
├─ MemoryExample 객체 (obj2)
│  ├─ instanceCount = 2
│  └─ instanceName = null
└─ "Global" 문자열 객체

Stack (main 스레드)
└─ main() Frame
   ├─ x = 10
   ├─ y = 20
   ├─ z = 30
   ├─ obj1 → Heap의 첫 번째 객체 주소
   └─ obj2 → Heap의 두 번째 객체 주소

핵심 정리

  1. Static 영역: 클래스 정보 + static 변수 (모든 스레드 공유)
  2. Heap 영역: 객체 + 배열 (모든 스레드 공유, GC 대상)
  3. Stack 영역: 지역 변수 + 메서드 호출 (스레드별 독립)

가장 중요한 포인트는 다음과 같습니다:

  • Primitive 타입 지역 변수: Stack에 값 직접 저장
  • Reference 타입 지역 변수: Stack에 주소만, 실제 객체는 Heap
  • Static 변수: Static 영역에 저장, 모든 객체가 공유
  • 인스턴스 변수: Heap의 객체 안에 저장, 객체마다 별도 공간

마무리

이번 시간에는 JVM의 메모리 구조에 대해 알아봤습니다.

처음에는 복잡해 보이지만, 실제 코드를 작성하면서 "이 변수는 어디에 저장되지?"라고 생각하다 보면 자연스럽게 이해가 됩니다.

특히 멀티스레드 환경에서 프로그래밍할 때, Heap과 Static은 공유되지만 Stack은 독립적이라는 점을 꼭 기억하세요!

꼭 실습을 통해 학습하신 걸 추천드리겠습니다!!

참고 자료