자바 개발자라면 누구나 한 번쯤 "내 코드가 메모리 어디에 저장될까?"라는 의문이 들기 마련입니다. 특히 면접에서 단골로 등장하는 JVM 메모리 구조는 단순히 명칭을 외우는 것이 아니라, 데이터가 흐르는 길을 이해하는 것이 핵심입니다.
오늘은 자바 메모리의 3대장인 Stack, Heap, Method Area가 각각 무엇을 담당하고, 실제 코드 실행 시 어떻게 움직이는지 상세한 시나리오와 함께 파헤쳐 보겠습니다.
1. 자바 메모리의 3대 핵심 영역
자바 프로그램이 실행되면 JVM은 OS로부터 메모리를 할당받아 이를 용도에 따라 크게 세 구역으로 나누어 관리합니다.
① Method Area (메서드 영역) - "공유 설계도 보관소"
프로그램이 시작될 때 클래스 로더가 .class 파일을 읽어 이곳에 저장합니다.
- 저장 데이터: 클래스 이름, 부모 클래스 이름, 메서드와 필드의 정보, static(정적) 변수.
- 특징: 프로그램 전체에서 공유되는 공간이며, 프로그램이 종료될 때까지 데이터가 유지됩니다. 붕어빵으로 비유하자면 '붕어빵 틀'에 해당합니다.
② Stack (스택) - "빠르고 정확한 개인 작업대"
메서드가 호출될 때마다 해당 메서드만을 위한 공간(Stack Frame)이 생깁니다.
- 저장 데이터: 기본형 변수(int, double 등)의 실제 값, 참조형 변수의 주소값(Reference).
- 특징: 각 스레드마다 독립적으로 가지는 공간이며, 메서드 실행이 끝나면 즉시 모든 데이터가 사라집니다. (LIFO 구조)
③ Heap (힙) - "자유로운 공용 창고"
new 연산자로 생성된 모든 객체와 배열이 거주하는 공간입니다.
- 저장 데이터: 실제 인스턴스(객체) 데이터.
- 특징: 모든 스레드가 공유하며, 더 이상 누구도 찾지 않는 데이터는 가비지 컬렉터(GC)가 수거해갑니다. 붕어빵 틀로 찍어낸 '실제 붕어빵'들이 보관되는 곳입니다.
2. 실제 시나리오로 보는 메모리 흐름 (Trace)
아래의 주식 정보 관리 코드가 실행될 때, 메모리 내부에서 어떤 변화가 일어나는지 단계별로 추적해 보겠습니다.
public class StockApp {
static String market = "KOSPI"; // (1) Method Area
public static void main(String[] args) {
int count = 10; // (2) Stack
Stock samsung = new Stock("삼성전자", 80000); // (3) Stack & Heap
}
}
🛠 단계별 메모리 변화
- 클래스 로딩 시점:
- JVM이 StockApp 클래스를 읽는 순간, Method Area에 클래스 정보가 등록되고 static 변수인 market에 "KOSPI"라는 값이 할당됩니다. 이 값은 프로그램이 끝날 때까지 사라지지 않습니다.
- main 메서드 실행:
- Stack 영역에 main 메서드를 위한 프레임이 생깁니다.
- int count = 10; 라인이 실행되면서 Stack에 직접 10이라는 값이 저장됩니다.
- 객체 생성 시점 (new Stock):
- new 키워드를 만나는 순간, Heap 영역의 빈 공간을 찾아 "삼성전자", 80000이라는 데이터를 가진 Stock 객체가 생성됩니다.
- 이때 Stack에 있는 samsung이라는 변수는 실제 데이터가 아닌, Heap에 있는 객체를 찾아갈 수 있는 주소값만 가집니다.
- 메서드 종료 시점:
- main 메서드가 끝나면 Stack에 있던 count와 samsung(주소값)은 즉시 삭제됩니다.
- 하지만 Heap에 있는 '삼성전자' 객체는 당장 사라지지 않습니다. 나중에 가비지 컬렉터가 돌면서 "이 주소를 가리키는 녀석이 이제 없네?"라고 판단할 때 비로소 삭제됩니다.
3. 왜 이렇게 복잡하게 나누어 관리할까?
효율적인 자원 분배 때문입니다.
- 속도 중심 (Stack): 크기가 작고 고정적인 데이터는 Stack에서 빠르게 처리하고 바로 비웁니다.
- 유연성 중심 (Heap): 크기가 얼마나 커질지 모르는 데이터는 넓은 Heap에 두고, 필요할 때만 주소값으로 찾아가서 씁니다.
- 공유 중심 (Method Area): 모든 객체가 공통으로 알아야 할 정보(설계도, static 변수)는 한 곳에 모아 메모리 낭비를 방지합니다.
4. Java 8 이후의 변화: Metaspace (심화 팁)
과거(Java 7 이전)에는 메서드 영역이 Heap의 일부인 PermGen 영역에 있었으나, 메모리 부족 에러가 자주 발생했습니다. Java 8부터는 이를 개선하여 Metaspace라는 영역으로 분리하고, 클래스 메타데이터를 OS가 관리하는 기본 메모리(Native Memory) 영역에 저장하여 유연성을 높였습니다.
단, Static 변수와 String Constant Pool은 여전히 Heap 영역에서 관리되어 GC의 혜택을 받습니다.
🏁 마무리하며: "메모리를 알면 코드가 보인다"
단순히 new를 써서 객체를 만드는 것과, "아, 지금 Heap에 데이터가 쌓이고 Stack에 주소값이 생겼겠구나"라고 생각하며 코딩하는 것은 하늘과 땅 차이입니다. 이 구조를 이해하면 나중에 다룰 가비지 컬렉션(GC) 최적화나 멀티 스레드 동기화 문제도 훨씬 쉽게 풀 수 있습니다.
'TECH > 웹개발' 카테고리의 다른 글
| [Spring Boot] API 예외 처리의 정석: @RestControllerAdvice로 클린 코드 만들기 (0) | 2026.03.13 |
|---|---|
| [Java] "돈 계산할 때 double 쓰면 망합니다" — BigDecimal의 필요성과 사용법 (0) | 2026.03.11 |
| [Java] String vs StringBuilder vs StringBuffer: 아직도 헷갈린다면 이 글 하나로 끝내세요 (0) | 2026.03.09 |
| [Java] Optional 완벽 가이드: NPE(NullPointerException)와 이별하는 깔끔한 방법 (0) | 2026.03.07 |
| [Java 21] 자바 서버 성능의 혁명: 가상 스레드(Virtual Threads) 구조와 실무 활용 가이드 (1) | 2026.03.03 |