메모리 구조 (스택, 힙, 데이터 영역)
선수학습(1개)
요약
프로그램의 메모리 구조를 알아봅니다. 스택(Stack)과 힙(Heap)의 차이, 컴파일 시점과 런타임의 메모리 할당 차이를 이해하면 동적 메모리 할당과 Garbage Collection을 쉽게 이해할 수 있습니다.
메모리 구조 핵심 정리
아래에서 하나씩 배울 내용을 미리 정리한 표입니다. "컴파일 시"와 "런타임 시"의 차이가 궁금하다면 컴파일과 런타임의 차이를 참고하세요.
메모리 구조 핵심 정리
아래에서 하나씩 배울 내용을 미리 정리한 표입니다. "컴파일 시"와 "런타임 시"의 차이가 궁금하다면 컴파일과 런타임의 차이를 참고하세요.
메모리 구조 핵심 정리
아래에서 하나씩 배울 내용을 미리 정리한 표입니다.
| 영역 | 저장 내용 | 크기 결정 | 해제 방식 |
|---|---|---|---|
| 코드(Code) | 바이트코드 (.pyc) | 임포트 시 | - |
| 데이터(Data) | 전역 변수, 모듈 레벨 객체 | 런타임 시 | 프로그램 종료 시 |
| 스택(Stack) | 함수 호출 프레임1 (지역 변수의 참조) | 런타임 시 | 자동 (함수 종료 시) |
| 힙(Heap) | 모든 객체 (정수, 문자열, 리스트 등) | 런타임 시 | 자동 (GC가 회수) |
프로그램 메모리 구조
C 프로그램이 실행되면 운영체제4가 메모리를 4개 영역으로 나누어 할당합니다.
Java 프로그램이 실행되면 JVM5이 메모리를 여러 영역으로 나누어 관리합니다. 이 페이지에서는 C와 비교하기 쉽도록 코드, 데이터, 힙, 스택의 4영역으로 나누어 설명합니다.6
Python 프로그램이 실행되면 CPython7 인터프리터가 메모리를 관리합니다. Python에서는 모든 값이 객체이므로, 정수나 문자열까지 포함해 대부분의 데이터가 힙에 저장됩니다.

힙과 스택은 메모리의 빈 공간에서 서로 마주보며 자랍니다. 힙은 낮은 주소8에서 높은 주소로, 스택은 높은 주소에서 낮은 주소로 확장되다가 만나면 메모리 부족(Out of Memory) 이 발생합니다.
| 영역 | 설명 |
|---|---|
| 코드 영역 | 컴파일된 바이트코드(.pyc)가 저장됩니다 |
| 데이터 영역 | 전역 변수와 모듈 레벨 객체가 저장됩니다 |
| 힙 영역 | 모든 객체(정수, 문자열, 리스트 등)가 저장됩니다 |
| 스택 영역 | 함수 호출 프레임1과 지역 변수의 참조가 저장됩니다 |
왜 이런 순서일까?
- 코드/데이터 영역이 낮은 주소8에 있는 이유: 프로그램 시작 시 가장 먼저 로드되고, 크기가 고정되어 있어서 0번부터 순서대로 자리를 잡아둡니다.
- 힙과 스택이 양 끝에서 자라는 이유: 둘 다 크기가 가변적이므로, 빈 공간을 최대한 활용하기 위해 반대 방향으로 자랍니다.
스택(Stack) 영역
스택의 특징
- LIFO(Last In First Out): 나중에 들어온 데이터가 먼저 나갑니다. 접시를 쌓을 때 마지막에 올린 접시를 먼저 꺼내는 것과 같습니다.
- 자동 할당/해제: 함수가 호출되면 할당, 종료되면 자동 해제
- 크기 고정: 컴파일 시점에 크기가 결정됩니다.
int a는 4바이트,int arr[5]는 20바이트처럼 코드만 보면 알 수 있습니다.
![func() 프레임 안에 a(int, 10)와 arr(int[5], 원소는 쓰레기값, 주소 100~104)가 있고 함수가 끝나면 프레임째 자동 해제되는 다이어그램](/_next/image?url=%2Fcoding%2Fc-language%2Fmemory-structure%2Fms-c-stack-func.webp&w=3840&q=75&dpl=dpl_8re87gb3Q12VGzGu9YovkgN6Y6Jo)
![func()의 스택 프레임에 a(int, 10)와 arr(int[] 주소, 주소100)가 있고 arr이 힙의 int[5] 배열을 가리키는 다이어그램](/_next/image?url=%2Fcoding%2Fjava%2Fmemory-structure%2Fms-java-stack-func-frame.webp&w=3840&q=75&dpl=dpl_8re87gb3Q12VGzGu9YovkgN6Y6Jo)
위 예시처럼 지역 변수라면 기본형10(int, double 등) 변수에는 값이 직접, 참조형 변수에는 참조값(주소) 만 스택에 저장되고, 객체와 배열 자체는 힙에 생성됩니다.
![func()의 지역 변수 a(10), s(주소100), nums(주소200)는 스택에 있고, Student 객체의 score·name 필드와 int[] 배열의 원소는 힙의 객체·배열 안에 저장되는 다이어그램](/_next/image?url=%2Fcoding%2Fjava%2Fmemory-structure%2Fjava-local-vs-field-storage.webp&w=3840&q=75&dpl=dpl_8re87gb3Q12VGzGu9YovkgN6Y6Jo)

Python에서는 모든 값이 객체입니다. a = 10이라고 써도 정수 10은 힙에 생성된 객체이고, 스택에는 그 객체를 가리키는 참조(이름) 만 저장됩니다.
스택 오버플로우
스택 크기는 제한되어 있어서, 너무 큰 배열을 선언하거나 재귀 호출이 너무 깊으면 스택 오버플로우(Stack Overflow) 가 발생합니다. 스택 메모리가 한계를 넘으면 운영체제가 프로그램을 강제 종료시킵니다.

스택 크기는 제한되어 있어서, 재귀 호출이 너무 깊으면 StackOverflowError가 발생합니다.

Java에서는 C와 달리 큰 배열을 선언해도 스택 오버플로우가 발생하지 않습니다. 배열은 항상 힙에 생성되기 때문입니다.
스택 크기는 제한되어 있어서, 재귀 호출이 너무 깊으면 RecursionError가 발생합니다. Python은 기본적으로 재귀 깊이를 1,000회로 제한합니다.

데이터(Data) 영역
데이터 영역은 프로그램이 시작할 때 할당되고, 종료될 때 해제되는 영역입니다. 전역 변수와 static 변수가 이 영역에 저장됩니다.

static 변수는 함수 안에 선언되어 있지만, 데이터 영역에 저장되어 함수가 종료되어도 값이 유지됩니다.
데이터 영역(메서드 영역)은 JVM이 클래스를 로딩할 때 할당됩니다. static 필드와 클래스 메타데이터가 이 영역에 저장됩니다.

static 필드는 객체가 아니라 클래스에 속하는 변수입니다. 모든 인스턴스가 같은 값을 공유합니다.
데이터 영역에는 모듈 레벨(파일 최상위)에서 정의된 전역 변수와 클래스 객체가 저장됩니다.

Python의 클래스 속성은 Java의 static 필드와 비슷하게, 모든 인스턴스가 같은 값을 공유합니다.
힙(Heap) 영역
힙 은 프로그래머가 직접 관리하는 동적 메모리 영역입니다.
힙의 특징
- 런타임 할당: 프로그램 실행 중에 크기 결정 가능
- 수동 관리:
malloc()으로 할당,free()로 해제 (#include <stdlib.h> 필요) - 유연한 크기: 스택보다 훨씬 큰 데이터도 저장 가능 (시스템 메모리만큼)

메모리 누수
free()를 호출하지 않으면 메모리 누수(Memory Leak) 가 발생합니다.
메모리 누수란? malloc()으로 할당받은 메모리를 free()로 반환하지 않아서, 더 이상 사용하지 않는 메모리가 힙에 계속 쌓이는 현상입니다.

왜 문제가 되나요?
| 상황 | 결과 |
|---|---|
| 단발성 프로그램 | 종료 시 OS가 모든 메모리 회수 → 큰 문제 없음 |
| 장시간 실행 (서버, 게임 등) | 메모리가 계속 쌓여서 시스템이 느려지거나 다운 |
메모리 누수 방지: malloc() 호출 횟수와 free() 호출 횟수가 항상 일치해야 합니다.

힙 은 new 키워드로 생성한 모든 객체와 배열이 저장되는 영역입니다.
힙의 특징
- 런타임 할당: 프로그램 실행 중에 객체 생성
- 자동 관리: Garbage Collector(GC)가 미사용 객체를 자동 회수
- 모든 객체가 힙에 저장:
new로 생성한 것은 모두 힙에 위치
![스택의 name과 scores가 힙의 String 객체와 int[100] 배열을 가리키고 문자열 풀에는 리터럴 gamja가 따로 있는 다이어그램](/_next/image?url=%2Fcoding%2Fjava%2Fmemory-structure%2Fms-java-heap-objects.webp&w=3840&q=75&dpl=dpl_8re87gb3Q12VGzGu9YovkgN6Y6Jo)
Java에서는 C와 달리 free()를 호출할 필요가 없습니다. Garbage Collector가 더 이상 참조되지 않는 객체를 자동으로 회수합니다.
String 리터럴은 힙 내부의 String Pool 이라는 특별한 공간에 저장됩니다. 자세한 내용은 문자열의 기초를 참고하세요.
힙 은 Python의 모든 객체가 저장되는 영역입니다. 정수, 문자열, 리스트, 딕셔너리까지 전부 힙에 생성됩니다.
힙의 특징
- 런타임 할당: 프로그램 실행 중에 객체 생성
- 자동 관리: Reference Counting + GC가 미사용 객체를 자동 회수
- 모든 것이 객체: 숫자, 문자열, 함수까지 전부 힙에 저장

Python에서는 C/Java와 달리 기본형(primitive type)이 없습니다. 42조차 int 객체이며 힙에 저장됩니다. 다만 자주 사용하는 작은 정수(-5 ~ 256)는 미리 캐싱해두어 재사용합니다.
스택 vs 힙 비교

| 구분 | 스택 | 힙 |
|---|---|---|
| 할당 방식 | 자동 | malloc() |
| 해제 방식 | 자동 (함수 종료) | free() 호출 |
| 크기 결정 | 컴파일 시 | 런타임 시 |
| 속도 | 빠름 (포인터만 이동) | 느림 (빈 공간 탐색) |
| 크기 제한 | 작음 (보통 1 ~ 8MB) | 큼 (시스템 메모리) |
| 사용 예 | 지역 변수, 배열 | 동적 배열, 연결 리스트 |
| 구분 | 스택 | 힙 |
|---|---|---|
| 저장 대상 | 함수 호출 프레임, 참조(이름) | 모든 객체 (정수, 문자열, 리스트 등) |
| 할당 방식 | 자동 (함수 호출 시) | 자동 (객체 생성 시) |
| 해제 방식 | 자동 (함수 종료) | GC가 자동 회수 |
| 크기 제한 | 재귀 깊이 1,000회 (기본값) | 시스템 메모리 |
컴파일 시점 vs 런타임
일반 변수와 배열은 컴파일할 때 크기가 결정되고, 동적 메모리는 런타임(실행 중) 에 결정됩니다. 자세한 내용은 컴파일과 런타임의 차이를 참고하세요.
![스택에 크기가 고정된 arr(int[10])와 n(5), p가 있고 p가 런타임에 n개 크기로 할당된 힙 블록을 가리키는 다이어그램](/_next/image?url=%2Fcoding%2Fc-language%2Fmemory-structure%2Fms-c-compile-vs-runtime.webp&w=3840&q=75&dpl=dpl_8re87gb3Q12VGzGu9YovkgN6Y6Jo)
동적 메모리 할당을 사용하면 사용자 입력이나 조건에 따라 실행 중에 크기를 결정할 수 있습니다.
왜 동적 메모리 할당이 필요한가?
크기를 미리 알 수 없는 경우

큰 데이터를 저장해야 하는 경우

유연한 자료구조 구현
연결 리스트처럼 크기가 변하는 자료구조는 동적 메모리 할당이 필수입니다.

다음 단계
메모리 구조를 이해했다면, 동적 메모리 할당에서 malloc()과 free() 함수의 구체적인 사용법을 학습하세요.
Garbage Collection (가비지 컬렉션)
Java는 C와 달리 프로그래머가 메모리를 직접 해제하지 않습니다. Garbage Collector(GC) 가 힙 영역에서 더 이상 참조되지 않는 객체를 자동으로 찾아서 회수합니다.

C와의 차이점
| 구분 | C | Java |
|---|---|---|
| 메모리 해제 | free() 수동 호출 | GC가 자동 회수 |
| 메모리 누수 | free() 누락 시 발생 | GC 덕분에 거의 발생하지 않음 |
| 개발 편의성 | 메모리 관리에 신경 써야 함 | 메모리 관리를 JVM에 위임 |
다음 단계
Java의 메모리 구조를 이해했다면, 문자열의 기초에서 String Pool과 == vs equals() 비교를 학습하세요.
Garbage Collection (가비지 컬렉션)
Python은 Reference Counting(참조 카운팅) 을 기본 메모리 관리 방식으로 사용합니다. 객체를 참조하는 변수가 0개가 되면 즉시 메모리를 해제합니다.

id() 함수로 객체 확인하기
id() 함수는 객체의 메모리 주소(고유 식별자) 를 반환합니다. 두 변수가 같은 객체를 가리키는지 확인할 수 있습니다.

C, Java와의 차이점
| 구분 | C | Java | Python |
|---|---|---|---|
| 메모리 해제 | free() 수동 호출 | GC가 자동 회수 | Reference Counting + GC |
| 메모리 누수 | free() 누락 시 발생 | 거의 발생하지 않음 | 거의 발생하지 않음 |
| 기본형 값의 저장 위치 | 지역 변수면 스택 | 지역 변수면 스택, 필드·배열 원소면 객체 안(힙) | 힙 (모든 것이 객체) |
다음 단계
Python의 메모리 구조를 이해했다면, 문자열의 기초에서 인덱싱, 슬라이싱 등 Python 문자열 핵심 개념을 학습하세요.
연습 문제
Footnotes
-
함수(메서드) 호출 정보란 함수가 호출될 때 스택에 쌓이는 데이터를 말합니다. 매개변수, 지역 변수, 그리고 함수 실행이 끝난 뒤 돌아갈 위치(리턴 주소)가 하나의 스택 프레임(Stack Frame) 으로 묶여 저장됩니다. 함수가 종료되면 해당 프레임이 스택에서 제거됩니다. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
클래스 메타데이터란 클래스 자체의 정보(클래스 이름, 필드 목록, 메서드 목록 등)를 말합니다. 객체를 만들기 위한 "설계도 정보"라고 생각하면 됩니다. ↩
-
Java 컴파일러는 메서드마다 지역 변수 칸 수와 피연산자 스택 크기를 미리 계산해 바이트코드에 기록합니다. 그래서 스택 프레임의 크기는 컴파일 시 정해지지만, 프레임 자체는 메서드가 호출될 때 스택에 만들어집니다. ↩ ↩2
-
운영체제(OS)는 Windows, macOS, Linux 같이 컴퓨터의 하드웨어를 관리하고, 프로그램이 실행될 수 있는 환경을 제공하는 소프트웨어입니다. ↩
-
JVM(Java Virtual Machine)은 Java 프로그램을 실행하는 가상 머신입니다. Java 소스 코드는 컴파일러가 바이트코드(.class)로 변환하고, JVM이 이 바이트코드를 해석하여 운영체제 위에서 실행합니다. 덕분에 같은 Java 코드가 Windows, Mac, Linux 어디서든 동작합니다. ↩
-
JVM 규격의 실제 런타임 영역은 메서드 영역, 힙, JVM 스택 등으로 정의되며 C의 메모리 구조와 똑같지는 않습니다. 여기서는 C와 비교하기 위해 단순화한 구분입니다. ↩
-
CPython은 Python의 공식 표준 구현체로, C 언어로 작성된 인터프리터입니다.
python명령어로 실행하면 기본적으로 CPython이 동작합니다. Python 코드를 바이트코드(.pyc)로 변환한 뒤 이를 해석하여 실행하며, 메모리 관리에 참조 카운팅(Reference Counting) 방식을 사용합니다. ↩ -
메모리 주소란 메모리의 각 칸에 붙은 고유 번호입니다. 0번, 1번, 2번... 순서대로 번호가 매겨져 있고, "낮은 주소"는 0에 가까운 번호, "높은 주소"는 큰 번호를 의미합니다. ↩ ↩2
-
지역 변수란 함수(메서드) 안에서 선언된 변수입니다. 함수가 호출될 때 생성되고, 함수가 종료되면 자동으로 사라집니다. 함수 바깥에서는 접근할 수 없습니다. ↩ ↩2 ↩3
-
기본형(Primitive Type)은
int,double,char,boolean등 값 자체를 직접 저장하는 자료형입니다. 반대로String,int[], 클래스 객체 등은 참조형(Reference Type)으로, 변수에는 객체를 가리키는 참조값(주소)만 저장되고 객체 자체는 힙에 생성됩니다. 참조형 변수가 지역 변수면 그 참조값은 스택에, 필드나 배열 원소면 그 객체·배열 안(힙)에 저장됩니다. ↩