
💡 이 글을 읽으면 알 수 있는 것
- OOM이나 StackOverflowError가 발생했을 때, 힙 외에 어떤 영역을 확인해야 하는지 알 수 있어요.
- 스레드마다 따로 만들어지는 영역과 모든 스레드가 함께 사용하는 영역의 차이를 이해할 수 있어요.
- JVM이 자바 프로그램을 실행할 때 메모리를 어떻게 나누어 관리하는지 전체 그림을 그릴 수 있어요.
자바 메모리를 이야기할 때 가장 먼저 떠오르는 것은 보통 힙(Heap)과 가비지 컬렉션(Garbage Collection, GC)이에요.
실제로 많은 객체가 힙에 생성되고, GC도 주로 힙을 기준으로 동작하므로 이 관점은 중요해요.
하지만 자바 프로그램의 실행 과정을 힙만으로 설명하기는 어려워요.
메서드 호출은 스택(Stack)에서 관리하고, 현재 실행 중인 명령어 위치는 별도로 추적해요.
클래스 정보와 상수 정보도 각각 다른 영역에 저장해요.
이 구조를 모르면 메모리 문제가 생겼을 때 원인을 정확히 좁히기 어려워요.
메모리 관련 오류가 발생했다고 해서 항상 힙이 부족한 것은 아니기 때문이에요.
이번 글에서는 JVM이 실행 중에 사용하는 런타임 데이터 영역(Runtime Data Areas)을 중심으로, 각 영역의 역할과 그 안에 어떤 데이터가 저장되는지 살펴볼게요.
JVM 런타임 데이터 영역 한눈에 보기

JVM은 자바 바이트코드를 실행할 때 여러 메모리 영역을 사용해요.
이 영역들을 묶어서 런타임 데이터 영역(Runtime Data Areas)이라고 불러요.
말 그대로, 프로그램이 실행되는 동안 JVM이 데이터를 저장하고 관리하는 공간이에요.
이 구조를 이해할 때 먼저 봐야 할 기준은 하나예요.
각 영역이 스레드마다 따로 만들어지는지, 아니면 여러 스레드가 함께 사용하는지예요.
- 스레드 고유 영역: 프로그램 카운터(Program Counter Register), 자바 가상 머신 스택(Java Virtual Machine Stack), 네이티브 메서드 스택(Native Method Stack)
- 스레드 공유 영역: 자바 힙(Java Heap), 메서드 영역(Method Area), 런타임 상수 풀(Runtime Constant Pool)
참고로 다이렉트 메모리(Direct Memory)는 JVM 명세에 정의된 공식 런타임 데이터 영역은 아니에요.
하지만 실제 메모리 문제를 이해할 때 자주 함께 살펴봐야 해요!
메서드 호출 흐름이나 현재 실행 위치처럼 스레드별로 따로 관리해야 하는 정보는 스레드 고유 영역에 저장해요.
반대로 객체 인스턴스나 클래스 메타데이터처럼 여러 스레드가 함께 참조하는 정보는 공유 영역에 저장해요.
이제 각 영역이 어떤 역할을 하는지 하나씩 살펴볼게요.
스레드 고유 영역: 독립적인 실행 흐름 유지

스레드 고유 영역은 각 스레드의 실행 흐름을 따로 관리하기 위한 정보를 담고 있어요.
객체를 장기간 저장하는 공간이라기보다, 현재 실행 상태를 추적하고 호출 흐름을 이어 가는 작업 공간으로 이해하면 좋아요.
프로그램 카운터 (Program Counter Register)
프로그램 카운터는 현재 스레드가 실행 중인 바이트코드 명령어의 위치를 나타내는 값이에요.
자바 프로그램은 순서대로만 실행되지 않아요.
조건문, 반복문, 예외 처리처럼 실행 흐름이 바뀌는 모든 과정은 결국 지금 어떤 명령어를 실행하고 있는지를 알아야 성립해요.
프로그램 카운터는 이 실행 위치를 기록해요.
덕분에 운영체제의 스케줄링으로 스레드가 번갈아 실행되더라도, 각 스레드는 자신의 실행 지점을 잃지 않고 다시 이어서 작업할 수 있어요.
자바 가상 머신 스택
JVM 스택은 자바 메서드의 호출과 실행 흐름을 관리하는 영역이에요.
메서드가 하나 호출될 때마다 스택에는 그 메서드 전용 스택 프레임(Stack Frame)이 생성돼요.
이 프레임에는 지역 변수 테이블(Local Variable Table), 피연산자 스택(Operand Stack), 동적 링크(Dynamic Linking), 메서드 반환 정보가 들어 있어요.
재귀 호출에서 문제가 생기는 이유도 이 구조로 설명할 수 있어요.
메서드를 호출할 때마다 새로운 스택 프레임이 계속 쌓이기 때문이에요.
이렇게 호출 깊이가 너무 깊어져 스택이 감당할 수 있는 범위를 넘어서면 StackOverflowError가 발생해요.
힙이 객체 인스턴스를 저장하는 영역이라면, JVM 스택은 메서드의 호출 흐름과 실행 문맥을 관리하는 영역이라고 볼 수 있어요.
네이티브 메서드 스택
JVM 스택이 자바 바이트코드로 작성된 메서드의 실행을 관리한다면, 네이티브 메서드 스택은 자바 밖의 네이티브 코드(C/C++ 등)로 작성된 메서드 실행을 위한 영역이에요.
자바 애플리케이션은 JNI(Java Native Interface)를 통해 외부 언어로 작성된 함수를 호출할 수 있어요.
이때 네이티브 메서드 스택은 네이티브 메서드를 실행하는 데 필요한 호출 흐름과 실행 문맥을 유지해요.
스레드 공유 영역: 프로그램 전체의 공통 데이터

스레드 공유 영역은 여러 스레드가 함께 참조해야 하는 데이터를 저장하는 공간이에요.
생성된 객체 인스턴스, 로드된 클래스의 메타데이터, 상수 정보가 여기에 포함돼요.
자바 힙 (Java Heap)
자바 힙은 객체 인스턴스와 배열을 저장하는 영역이에요.
자바 애플리케이션에서 생성되는 대부분의 객체는 힙에 저장돼요.
그래서 우리가 흔히 메모리 사용량을 이야기할 때 가장 먼저 떠올리는 영역이기도 해요.
힙은 여러 스레드가 함께 참조하는 객체를 저장하므로 스레드 공유 영역에 속해요.
또한 더 이상 참조되지 않는 객체를 정리하는 가비지 컬렉션(Garbage Collection, GC)과 가장 밀접하게 연결된 영역이에요.
객체가 과도하게 생성되거나 메모리 누수가 발생하면 힙 사용량이 계속 증가해요.
이 상태가 한계를 넘어서면 OutOfMemoryError가 발생할 수 있어요.
메서드 영역 (Method Area)
메서드 영역은 힙과 달리 클래스 수준의 정보를 저장하는 영역이에요.
이곳에는 클래스의 필드와 메서드 정보, 정적 변수, 타입 정보 같은 메타데이터가 저장돼요.
하나의 클래스 인스턴스가 힙에 여러 개 생성되더라도, 그 클래스의 공통 구조 정보는 메서드 영역에서 한 번만 관리해요.
참고로 JVM 명세에서 말하는 메서드 영역(Method Area)은 HotSpot JVM에서 구현 방식이 바뀌어 왔어요.
과거에는 PermGen을 사용했고, 현재는 Metaspace를 사용해요.
런타임 상수 풀 (Runtime Constant Pool)
런타임 상수 풀은 클래스 파일에 들어 있는 상수 풀(Constant Pool) 정보를 실행 중에 사용할 수 있도록 저장하고 관리하는 구조예요.
이 안에는 숫자나 문자열 같은 리터럴뿐 아니라, 클래스·메서드·필드 이름을 가리키는 심볼릭 레퍼런스(Symbolic Reference)도 포함돼요.
JVM은 이 정보를 바탕으로 실행 중에 필요한 대상을 찾고, 실제 참조로 해석해 연결해요.
런타임 상수 풀은 메서드 영역과 밀접하게 연결되어 동작해요.
그래서 클래스 정보를 이해할 때 함께 봐야 하는 중요한 구조예요.
번외: 다이렉트 메모리 (Direct Memory)
다이렉트 메모리는 JVM 명세에 정의된 공식 런타임 데이터 영역은 아니에요.
하지만 실무에서 자바 메모리를 이해할 때 함께 봐야 하는 중요한 영역이에요.
다이렉트 메모리는 JVM 힙 바깥의 오프 힙(Off-heap) 메모리를 직접 사용하는 방식을 말해요.
보통 NIO(New I/O)의 ByteBuffer와 함께 사용하며, 대용량 입출력이나 네트워크 처리 성능을 높이는 데 활용해요.
문제는 다이렉트 메모리를 과도하게 사용하면 힙 사용량이 정상이어도 메모리 부족이 발생할 수 있다는 점이에요.
그래서 메모리 장애를 분석할 때는 힙뿐 아니라 다이렉트 메모리도 함께 확인해야 해요.
한 눈에 비교하기
지금까지 살펴본 영역을 표로 나란히 비교해 보면 전체 구조를 더 쉽게 정리할 수 있어요.
| 영역 | 주된 저장 대상 | 스레드별/공유 여부 | 대표 오류·특징 | GC 연관성 |
| 프로그램 카운터 | 현재 실행 중인 바이트코드 위치 | 스레드별 | 자바 바이트코드 실행 위치 추적 | 거의 없어요 |
| 자바 가상 머신 스택 | 스택 프레임, 지역 변수, 피연산자 스택 | 스레드별 | StackOverflowError와 밀접해요 |
직접 대상은 아니에요 |
| 네이티브 메서드 스택 | 네이티브 메서드 실행 문맥 | 스레드별 | JNI, 네이티브 호출과 연결돼요 | 직접 대상은 아니에요 |
| 자바 힙 | 객체 인스턴스, 배열 | 공유 | OutOfMemoryError와 밀접하고, GC가 가장 활발하게 동작하는 영역이에요 |
매우 높아요 |
| 메서드 영역 | 클래스 메타데이터, 정적 변수 등 | 공유 | 명세상의 개념과 구현체의 차이를 함께 봐야 해요 (Metaspace) |
간접적으로 연관돼요 |
| 런타임 상수 풀 | 리터럴, 심볼릭 레퍼런스 | 공유 | 메서드·필드 참조 해석과 연결돼요 | 간접적으로 연관돼요 |
| 다이렉트 메모리 | 힙 바깥 버퍼 등(Off-heap) | 사용 방식에 따라 달라요 | NIO 성능 최적화에 활용되며, 힙과 다른 방식으로 관리돼요 | 힙 GC와는 달라요 |
결론적으로 자바 메모리는 힙 하나만으로 설명하기 어려워요.
메서드 호출은 스택에서 관리하고, 클래스 정보는 메서드 영역에 저장하며, 현재 실행 위치는 프로그램 카운터로 추적해요.
이 전체 구조를 함께 이해해야 OutOfMemoryError나 StackOverflowError 같은 문제를 더 정확하게 해석할 수 있어요.
이제 JVM 메모리의 전체 지도를 살펴봤으니, 다음 질문도 자연스럽게 떠오를거에요.
객체는 이 메모리 구조 위에서 실제로 어떻게 생성되고, 어디에 배치되며, 어떤 과정을 거쳐 살아남거나 사라질까요?
다음 글에서는 객체 생성 과정과 힙 안에서의 동작을 더 구체적으로 살펴볼게요!
'Language > Java ☕️' 카테고리의 다른 글
| JVM 메모리 장애 분석: OutOfMemoryError와 StackOverflowError를 제대로 이해하기 (0) | 2026.03.09 |
|---|---|
| 자바 객체는 메모리에 어떻게 배치될까요? 객체 생성 과정과 메모리 레이아웃 (0) | 2026.03.09 |
| Java static 키워드 완전 가이드 (0) | 2024.05.22 |
| 자바 메모리 구조 이해하기 (0) | 2024.05.22 |
| 자바의 가비지 컬렉션(GC) 한 번에 이해하기 (0) | 2024.02.02 |
안녕하세요, 저는 주니어 개발자 박석희 입니다. 언제든 하단 연락처로 연락주세요 😆