변수 하나, 객체 하나가 어디에 올라가고, 언제 지워지는지를 알면 성능·버그·GC가 한눈에 이해돼요. 자바의 스택·힙·메서드 영역부터 자바스크립트 메모리까지, 코드와 함께 차근차근 봅니다.
프로그램이 실행되면, 필요한 데이터(변수·객체)와 명령(코드)이 전부 메모리(RAM)에 올라가고, CPU가 그걸 읽으며 일해요. 그래서 "변수를 만든다", "객체를 생성한다"는 말은 사실 메모리 어딘가에 자리를 잡는다는 뜻이에요.
자바와 자바스크립트는 둘 다 메모리를 자동으로 관리해줘요(가비지 컬렉션). 그래서 C처럼 직접 반납하지 않아도 되지만, 어디에 뭐가 올라가고 언제 지워지는지를 알면 — 성능 문제, 메모리 누수, "왜 값이 안 바뀌지?" 같은 버그가 훨씬 잘 보여요.
자바 프로그램은 JVM(자바 가상 머신) 위에서 돌아요. JVM은 실행에 필요한 메모리를 아래처럼 런타임 데이터 영역(Runtime Data Area)으로 나눠 씁니다.
나머지 둘은 보조 역할이라 이름만 알아두면 돼요. PC 레지스터(현재 실행 중인 명령의 주소를 기억, 스레드마다 하나)와 네이티브 메서드 스택(자바가 아닌 C 등으로 짜인 코드를 부를 때 쓰는 스택)이에요.
이게 자바 메모리에서 가장 중요한 구분이에요. 기본형(primitive) 변수는 값 자체를 스택에 담고, 참조형(객체) 변수는 스택에 주소(참조)만 담고 실제 객체는 힙에 둬요.
int age = 20; // 기본형: 값 20이 스택에 그대로
int[] nums = new int[]{1, 2}; // 참조형: 배열 객체는 힙에, nums에는 그 '주소'만
| 구분 | 예 | 변수에 담기는 것 | 실제 데이터 위치 |
|---|---|---|---|
| 기본형 | int, long, double, char, boolean … | 값 그 자체 | 스택 |
| 참조형 | 객체, 배열, String … | 힙에 있는 객체의 주소 | 힙 |
Person a = new Person(); Person b = a; 하면, 객체가 복사되는 게 아니라 주소만 복사돼요. a와 b가 같은 힙 객체를 가리키니, b로 값을 바꾸면 a로 봐도 바뀌어 있어요.class Person {
String name;
int age;
Person(String name, int age) { this.name = name; this.age = age; }
}
public class Main {
static int count = 0; // static 변수
public static void main(String[] args) {
int a = 10; // (1)
Person p = new Person("Lee", 20); // (2)
greet(p); // (3)
} // (5) main 끝
static void greet(Person person) { // (3)
String msg = "Hi " + person.name; // (4)
System.out.println(msg);
} // (4-끝) greet 끝
}
int a = 10;Person p = new Person("Lee", 20);"Lee"는 힙의 문자열 상수 풀에 보관돼요.)
greet(p); 호출String msg = ...; 그리고 greet 끝println이 끝나고 greet가 return되면 — greet 프레임이 통째로 스택에서 사라져요(pop). msg와 person도 함께 없어져요. 하지만 힙의 Person 객체는 아직 p가 가리키고 있어 남아 있어요.
가비지 컬렉션(GC)은 힙에서 더 이상 쓰이지 않는 객체를 찾아 자동으로 메모리를 회수하는 기능이에요. 기준은 딱 하나 — "지금 도달할 수 있는가(reachable)"예요.
p = null; 처럼 참조를 끊으면, 그 객체는 도달 불가가 되어 GC 대상이 돼요.
대표 알고리즘은 마크 앤 스윕(Mark and Sweep)이에요. ① Root에서 닿는 객체를 전부 표시(mark)하고, ② 표시 안 된 것들을 쓸어(sweep) 회수해요. 이후 흩어진 공간을 모으는 압축(compact)을 하기도 해요.
경험적으로 새로 만든 객체 대부분은 아주 빨리 안 쓰이게 돼요(약한 세대 가설). 그래서 힙을 나이대로 나눠 관리해요.
| 영역 | 담기는 것 | 청소 |
|---|---|---|
| Young (Eden + Survivor) | 갓 태어난 객체 | Minor GC — 자주, 빠르게 |
| Old (Tenured) | Young에서 오래 살아남은 객체 | Major(Full) GC — 드물게, 무겁게 |
free 같은 게 없음). System.gc()로 "요청"은 할 수 있지만 강제는 아니에요 — 실제 시점은 JVM이 정해요. 우리가 할 일은 안 쓰는 참조를 오래 붙들지 않는 것뿐이에요.자바스크립트도 콜 스택(Call Stack)과 힙(Heap)을 써요. 자바와 마찬가지로 원시값은 스택에 값으로, 객체는 힙에 두고 변수엔 참조를 담아요.
let a = 10; // 원시값(number): 값 자체가 스택에
let obj = { x: 1 }; // 객체: 힙에 저장, obj엔 '참조'만
let obj2 = obj; // 참조 복사 → 같은 객체를 가리킴
obj2.x = 99;
console.log(obj.x); // 99 (같은 객체라 함께 바뀜)
let b = a; // 원시값은 값 복사
b = 50;
console.log(a); // 10 (원본은 그대로)
원시값(number, string, boolean, null, undefined, symbol, bigint)은 복사하면 값이 통째로 복사돼서 서로 독립이에요. 객체·배열·함수는 참조가 복사돼서 같은 대상을 공유해요 — 자바의 기본형 vs 참조형과 똑같은 원리죠.
GC가 있어도 "참조가 남아 있으면" 못 지워요. 그래서 안 쓰는데도 계속 참조가 붙어 있는 상황이 곧 누수예요.
// 1) 실수로 만든 전역 변수 — 프로그램 내내 안 지워짐
function f() { leak = new Array(1000000); } // var/let 빠뜨림 → 전역
// 2) 안 지운 타이머 — 콜백이 계속 살아 있음
const id = setInterval(doWork, 1000);
// 다 쓰면 clearInterval(id) 로 꼭 해제
// 3) 클로저가 큰 데이터를 계속 붙들고 있는 경우
function make() {
const big = new Array(1000000);
return () => big[0]; // big이 계속 참조됨 → GC 못 함
}
clearInterval, removeEventListener), 필요 없어진 큰 객체는 참조를 끊기(x = null), 전역 변수 남발 금지. 자바에서도 똑같이 — 안 쓰는 컬렉션에 객체를 계속 넣어두면 GC가 손대지 못해요.| 자바 | 자바스크립트 | |
|---|---|---|
| 실행 환경 | JVM | JS 엔진(V8 등) |
| 임시 작업 공간 | 스택(스레드마다) | 콜 스택 |
| 객체 저장 | 힙 | 힙 |
| 클래스·static·상수 | 메서드 영역 | (별도 영역 개념 없음, 엔진이 관리) |
| 메모리 정리 | GC (세대별·마크앤스윕) | GC (마크앤스윕) |
| 수동 해제 | 없음 | 없음 |
지금까지는 언어(자바·JS) 관점이었어요. 메모리는 더 아래(운영체제·하드웨어)와 옆(다른 언어)에서도 계속 등장해요. 방향만 잡아둘게요.
| 영역 | 핵심 개념 | 왜 알면 좋나 |
|---|---|---|
| 운영체제(OS) 메모리 | 가상 메모리, 페이징, 스와핑, 단편화, MMU | "힙·스택"이 실제 물리 메모리에 어떻게 매핑되는지의 바탕. (CS기초 · 메모리관리 챕터) |
| C / C++ 수동 메모리 | malloc/free, new/delete, 포인터, 참조 | GC가 대신 해주던 일을 직접 해보면 힙·스택이 확실해져요. 실수하면 누수·이중 해제·댕글링 포인터. |
| 스택 오버플로우 | 재귀가 너무 깊으면 스택 프레임이 한계 초과 | 재귀·함수 호출이 스택을 쓴다는 걸 체감. (자바 StackOverflowError) |
| 메모리 부족(힙) | 객체를 계속 쌓으면 힙 고갈 | 대용량 처리·누수의 결과. (자바 OutOfMemoryError) |
| 값 전달 vs 참조 전달 | pass by value / reference | "함수에 넘긴 값이 왜 안(또는 왜) 바뀌지?"의 답. 스택·힙 이해가 핵심. |
| 메모리 계층 | 레지스터 > 캐시 > RAM > 디스크 | "왜 캐시가 중요한가", 성능의 바탕. (CS기초 · 컴퓨터 구조 챕터) |