CS 스터디 (Java) 3 - JVM 의 메모리 영역과 할당 시점은?
JVM(Java Virtual Machine)은 자바 프로그램 실행 시 운영체제로부터 메모리를 할당받아, 실행 목적에 따라 5가지 주요 영역으로 구분하여 관리합니다. 각 영역은 데이터의 종류와 생명주기에 따라 메모리 할당 시점이 다릅니다. 각 주요 영역 별로 설명하겠습니다.
1. 메소드 영역
- 클래스 메타데이터, static 변수, 상수 풀(Run-Time Constant Pool), final 상수값 등이 저장됩니다.
- 클래스가 JVM에 처음 로딩될 때 메모리가 할당되며,
- 모든 스레드가 공유하며, 프로그램 시작부터 종료까지 유지됩니다.
- JDK 7까지는 PermGen(Permanent Generation), JVM 힙 내부에 고정 크기로 존재하여
- 많은 수의 클래스나 동적으로 로딩되는 클래스가 많은 경우 쉽게 가득 찼습니다.
- 이로 인해 OOM(OutOfMemoryError) 오류가 자주 발생하였으며, static 데이터를 사용하기 힘들었습니다.
- 이를 해결하기 위해, JDK 8부터는 Metaspace로 대체되어 native memory에 위치하고, 크기를 동적으로 확장할 수 있게 되었습니다.
** javap -v {클래스이름}.class 명령으로 클래스의 Constant Pool 확인 가능.
** Metaspace: JDK 8부터 PermGen을 대체한 영역으로, 역할은 동일하지만 JVM 힙이 아닌 native memory에 위치하며, 기본적으로 크기 제한이 없고 필요 시 자동 확장되는 장점이 있습니다.
2. 힙 영역
- new 연산자를 통해 생성된 모든 객체 인스턴스와 배열이 저장됩니다.
- 런타임 시점, 즉 클래스 실행 중 객체 생성 시점에 동적으로 메모리가 할당됩니다.
- 힙 영역은 GC(Garbage Collector)에 의해 관리되며, Young Generation(Eden, Survivor 0/1) 과 Old Generation으로 나뉩니다.
- 모든 스레드가 공유합니다.
String str1 = "abc"; // String Constant Pool (Heap의 일부)
String str2 = new String("abc"); // Heap 객체
System.out.println(str1 == str2); // false
- str1은 Constant Pool(Heap의 특수 영역)에 위치하고, str2는 일반 Heap 객체로 생성되어 서로 다른 참조값을 가지므로 false가 반환됩니다.
3. 스택 영역
- 메서드 호출 시 스택 프레임이 생성되고, 종료되면 종료되면 제거됩니다.
- 스택 프레임에는 지역 변수, 매개변수, 리턴 주소 등이 저장되며,
- 스레드마다 독립적으로 존재하며, LIFO(Last-In First-Out) 구조로 동작합니다.
- 이 영역은 JVM이 자동으로 할당(push) 및 해제(pop)하기 때문에, GC의 관리 대상이 아닙니다.
(GC는 Heap처럼 객체 참조 여부를 추적하여 수집하지만, Stack은 스코프 기반으로 프레임 명확히 소멸되므로 GC가 필요하지 않습니다.)
4. PC 레지스터(PC Register)
- PC 레지스터는 현재 실행 중인 JVM 바이트코드 명령어의 주소를 저장합니다.
- 스레드가 시작될 때 초기화되며, JVM이 명령어 단위로 실행 흐름을 제어합니다.
- 스레드마다 독립적으로 존재합니다.
5. 네이티브 메서드 스택(Native Method Stack)
- JNI(Java Native Interface)를 통해 호출되는 C/C++ 등 네이티브 코드를 실행할 때 사용되는 스택입니다.
- 네이티브 메서드 호출 시 매개변수, 지역 변수 등이 저장됩니다.
- 네이티브 메서드 스택은 스레드마다 존재하며, 필요 시 할당됩니다.
JVM 메모리 영역별 역할 및 GC 관리 여부 정리
영역 | 저장 내용 | 할당 시점 | 공유 여부 | GC 관리 대상 |
메소드 영역 | 클래스 메타데이터, static 변수 | 클래스 로딩 시 | 모든 스레드 공유 | ❌(JDK 8 Metaspace는 GC 일부 관리) |
힙 영역 | 객체, 배열, String Constant Pool | 런타임 (new) | 모든 스레드 공유 | ✅ |
스택 영역 | 지역 변수, 매개변수 | 메서드 호출 시 | 스레드별 | ❌ |
PC Register | JVM 명령어 주소 | 스레드 시작 시 | 스레드별 | ❌ |
네이티브 메서드 스택 | 네이티브 코드 실행 데이터 | 네이티브 메서드 실행 시 | 스레드별 | ❌ |
결론
JVM(Java Virtual Machine)의 메모리는 크게 5가지 주요 영역으로 구분됩니다. 각각의 영역은 목적에 따라 할당되는 시점이 다릅니다. 메소드 영역은 클래스 로딩 시 클래스의 메타 데이터, static 변수 상수 풀 등이 저장되는 영역으로, 클래스가 JVM에 처음 로딩 될 때 메모리 할당됩니다. 힙 영역은 애플리케이션에서 생성하는 모든 객체 인스턴스가 저장되는 영역으로 new 연산자를 통해 객체가 생성될 때마다 메모리가 동적으로 할당됩니다. 이 영역은 가비지 컬렉터의 관리 대상이며, 애플리케이션 성능에 큰 영향을 미칩니다. 스택 영역은 각 스레드마다 별도로 생성되며, 메서드가 호출될 때마다 스택 프레임이 생성되고, 메서드가 종료되면 프레임이 제거됩니다. 여기에는 지역 변수, 매개변수, 리턴 주소 등이 저장됩니다. 이 영역은 JVM이 자동으로 할당 및 해제되기 때문에 GC 대상이 아닙니다. PC Register는 현재 실행 중인 명령어의 주소를 저장하는 용도로 스레드마다 생성되며, 스레드가 시작될 때 초기화됩니다. Native Method Stack은 JNI(JAVA Native Interface)를 통해 호출되는 네이티브 메서드가 사용하는 스택으로, 스레드가 네이티브 코드를 실행할 때 할당됩니다.