메모리는 어떻게 사용될까? (Call Stack / Heap)

스노어·2026년 1월 13일

CS

목록 보기
2/6
post-thumbnail

지난 글에서는 JavaScript 프로그램이
디스크메모리CPU 순서로 실행된다는 흐름을 정리했다.

이번 글에서는 그중에서도
메모리(RAM)가 실제로 어떻게 사용되는지를 조금 더 깊게 살펴보겠다.


💡 왜 메모리 구조를 알아야 할까?

JavaScript 개발을 하다 보면 이런 상황을 자주 마주친다 :(

  • 함수가 어디서 호출됐는지 헷갈릴 때
  • 비동기 코드의 실행 순서가 이해되지 않을 때
  • 메모리 누수라는 말을 들었지만 감이 안 올 때

이 문제들의 중심에는 항상 메모리 구조가 있다.

JavaScript 실행 시 모든 데이터와 함수 호출은 정해진 메모리 영역에 저장되며,
그 구조를 이해하지 못하면 실행 흐름을 따라가기 어렵다.

📌 핵심: 메모리 구조를 이해하면 "왜 내 코드가 이렇게 동작하는가?" 에 대한 답을 찾을 수 있다.


🧠 메모리는 하나의 공간이 아니다

JavaScript 실행 환경에서 메모리는 크게 두 영역으로 나뉜다.

🧠 메모리(RAM)
├── Call Stack (콜 스택)
│   └── 함수 호출 순서와 실행 정보 관리
│
└── Heap ()
    └── 객체, 배열 등 실제 데이터 저장

이 두 영역은 역할도 다르고, 사용되는 방식도 완전히 다르다.

영역역할특징비유
Call Stack함수 호출 관리후입선출(LIFO) 구조책을 쌓는 더미
Heap데이터 저장자유로운 할당/해제창고 공간

📚 Call Stack이란?

💬 Call Stack(콜 스택) 이란?
함수 호출 순서를 관리하는 메모리 공간이다.
마치 책을 차곡차곡 쌓는 것처럼, 함수가 호출될 때마다 위로 쌓이고, 실행이 끝나면 위에서부터 제거된다.

Call Stack함수 호출 순서를 관리하는 공간이다.

JavaScript에서 함수가 호출되면 그 함수의 실행 정보가 Call Stack에 쌓인다.
그리고 함수 실행이 끝나면 가장 마지막에 들어온 함수부터 스택에서 제거된다.


🔄 LIFO (Last In, First Out) 구조

💬 LIFO(후입선출) 란?
Last In, First Out의 약자로, "마지막에 들어간 것이 먼저 나온다"는 의미다.
접시를 쌓아두고 사용할 때, 가장 위에 있는 접시를 먼저 꺼내는 것과 같은 원리다.

이 구조를 후입선출(LIFO) 이라고 한다.

즉,

  • 먼저 호출된 함수는 아래에
  • 나중에 호출된 함수는 위에 쌓인다
📚 Call Stack 구조 (LIFO)

[validate]3번째 호출, (가장 먼저 제거됨)
[getUser]2번째 호출
[main]1번째 호출,아래 (가장 나중에 제거됨)

이 때문에 "지금 어떤 함수가 실행 중인지"는 Call Stack맨 위를 보면 알 수 있다.


🔍 Call Stack에서 벌어지는 일 (구체적인 예시)

다음과 같은 코드를 실행한다고 가정해보자:

function validate(name) {
  if (!name) throw new Error("이름이 필요합니다");
  return true;
}

function getUser(name) {
  validate(name);
  return { name, age: 30 };
}

function main() {
  const user = getUser("Kim");
  console.log(user);
}

main();

이때 Call Stack에는 다음 순서로 정보가 쌓인다:

📊 실행 흐름 시각화

1️⃣ 프로그램 시작
Call Stack: [전역]

2️⃣ main() 호출
Call Stack: 
[main]      ← 현재 실행 중
[전역]

3️⃣ main 안에서 getUser("Kim") 호출
Call Stack:
[getUser]   ← 현재 실행 중
[main]
[전역]

4️⃣ getUser 안에서 validate(name) 호출
Call Stack:
[validate]  ← 현재 실행 중 ⭐
[getUser]
[main]
[전역]

5️⃣ validate 실행 완료 → Stack에서 제거
Call Stack:
[getUser]   ← 다시 실행 재개
[main]
[전역]

6️⃣ getUser 실행 완료 → Stack에서 제거
Call Stack:
[main]      ← 다시 실행 재개
[전역]

7️⃣ main 실행 완료 → Stack에서 제거
Call Stack:
[전역]

📌 핵심 원리: validate 함수의 실행이 끝나면 Call Stack의 맨 위에서 제거되고, 실행 흐름은 다시 getUser 함수로 돌아간다.


📝 Call Stack에 저장되는 정보

함수가 실행될 때 다음과 같은 정보들이 Call Stack에 저장된다:

저장 정보설명예시
함수 실행 위치코드의 어느 줄을 실행 중인지getUser 함수의 2번째 줄
매개변수 값함수에 전달된 인자name: "Kim"
지역 변수함수 내부에서 선언된 변수const user = {...}
반환 주소함수 실행 후 돌아갈 위치main 함수의 3번째 줄

⚠️ Stack Overflow 오류

💬 Stack Overflow 란?
Call Stack의 크기에는 제한이 있다. 함수 호출이 너무 깊게 중첩되거나 재귀 호출이 끝나지 않으면, Stack이 허용된 크기를 초과하여 발생하는 오류다.

그래서 함수가 깊게 중첩되거나 재귀 호출이 과도해지면 Call Stack이 넘칠 수 있다.

// ❌ 잘못된 재귀 함수 (종료 조건 없음)
function infiniteRecursion() {
  infiniteRecursion(); // 자기 자신을 계속 호출
}

infiniteRecursion();
// ⚠️ RangeError: Maximum call stack size exceeded
Call Stack 상태 (Stack Overflow 발생 직전)

[infiniteRecursion]10000번째 호출
[infiniteRecursion]
[infiniteRecursion]
[...]               ← 계속 쌓임
[infiniteRecursion]1번째 호출
[전역]
💥 Stack Overflow!

이때 발생하는 대표적인 오류가 Stack Overflow이다.


📦 Heap이란?

💬 Heap(힙) 이란?
객체, 배열처럼 크기가 가변적이고 구조가 복잡한 데이터를 저장하는 메모리 공간이다.
Call Stack처럼 정해진 순서 없이, 필요할 때마다 빈 공간에 자유롭게 할당된다.

Heap객체와 데이터가 저장되는 공간이다.

JavaScript에서 생성되는:

  • 객체 ({ name: "Kim" })
  • 배열 ([1, 2, 3])
  • 함수 객체 (function() {})
  • JSON 데이터처럼 구조와 크기가 변하는 값

들은 모두 Heap 영역에 저장된다.


🆚 Call Stack vs Heap의 차이점

HeapCall Stack처럼 정해진 순서로 쌓이지 않고,
필요할 때마다 빈 공간에 데이터를 할당하는 방식으로 사용된다.

📦 Heap 메모리 구조

┌─────────────────────────────────┐
│  빈 공간                         │
├─────────────────────────────────┤
│  { name: "Kim" }0x1234    │
├─────────────────────────────────┤
│  빈 공간                         │
├─────────────────────────────────┤
│  [1, 2, 3]0x5678    │
├─────────────────────────────────┤
│  빈 공간                         │
└─────────────────────────────────┘


💾 Heap에 저장되는 데이터의 예시

실제 개발 상황에서 Heap에 저장되는 데이터를 살펴보자:

// 1️⃣ 사용자 정보 객체
const user = {
  name: "Kim",
  age: 30,
  email: "kim@example.com"
};

// 2️⃣ 게시글 목록 배열
const posts = [
  { id: 1, title: "첫 번째 글" },
  { id: 2, title: "두 번째 글" },
  { id: 3, title: "세 번째 글" }
];

// 3️⃣ 서버에서 받아온 JSON 응답 데이터
const response = {
  status: "success",
  data: {
    users: [...],
    pagination: { page: 1, total: 100 }
  }
};

이런 데이터들은 실행 중에 크기나 내용이 계속 바뀔 수 있다.

예를 들어:

  • 사용자가 프로필을 수정하면 user 객체의 내용이 변경됨
  • 새 게시글이 추가되면 posts 배열의 크기가 늘어남
  • API 응답이 올 때마다 response 객체가 새로 생성됨

이런 가변적인 데이터들은 Call Stack이 아닌 Heap에 저장된다.


🔗 Call Stack과 Heap의 관계

그렇다면 Call StackHeap은 어떻게 연결되어 있을까?

💬 참조(Reference) 란?
실제 데이터의 위치를 가리키는 주소 정보다.
마치 "3층 302호"라는 주소가 실제 방을 가리키는 것처럼, 참조는 Heap에 있는 데이터의 위치를 가리킨다.

Call Stack에는 실제 객체 데이터가 저장되지 않는다.

대신:

  • Call Stack에는 참조 정보 (메모리 주소)
  • 실제 데이터는 Heap

에 저장된다.

📊 구체적인 예시로 이해하기

function getUser() {
  const user = { name: "Kim", age: 30 };
  return user;
}

const result = getUser();

이 코드가 실행될 때 메모리 상태:

📚 Call Stack                    📦 Heap
┌─────────────────┐             ┌──────────────────────┐
│ getUser()       │             │                      │
│  user → 0x1234  │────────────→│ 0x1234: {             │
└─────────────────┘             │   name: "Kim",        │
                              │   age: 30             │
┌─────────────────┐             │ }                     │
│ 전역             │           │                       │
│ result → 0x1234 │────────────→│ (같은 객체 참조)     │
└─────────────────┘             └──────────────────────┘

📌 핵심: Call Stackuser 변수와 result 변수는 둘 다 같은 Heap 메모리 주소(0x1234)를 참조한다.

즉:

  • 함수 실행 흐름은 Call Stack이 관리하고
  • 데이터 자체는 Heap이 관리한다

이 구조 덕분에 JavaScript는 여러 함수가 같은 객체를 공유하면서도 유연하게 동작할 수 있다.


🗑️ Garbage Collection은 언제 일어날까?

💬 Garbage Collection(가비지 컬렉션) 이란?
더 이상 사용되지 않는 메모리를 자동으로 해제하는 JavaScript 엔진의 기능이다.
마치 청소부가 쓰레기를 치우듯, 참조되지 않는 객체를 Heap에서 제거한다.

Heap에 저장된 데이터는 더 이상 참조되지 않으면 Garbage Collection의 대상이 된다.

✅ 정상적인 경우 (메모리 자동 해제)

function processUser() {
  const temp = { name: "Kim" }; // Heap에 객체 생성
  console.log(temp.name);
  // 함수 종료 → temp 참조 사라짐
} // ✅ Garbage Collection 대상이 됨

processUser();
실행 전:
📦 Heap: { name: "Kim" } ← temp가 참조 중

실행 후:
📦 Heap: (비어있음) ← 🗑️ GC가 자동 제거

❌ 메모리 누수 발생 경우

💬 메모리 누수(Memory Leak) 란?
더 이상 사용하지 않는 데이터가 메모리에 계속 남아있어, 사용 가능한 메모리가 점점 줄어드는 현상이다.

// ❌ 전역 변수에 저장 → 계속 참조됨
const userCache = {};

function loadUser(id) {
  const user = fetchUserFromDB(id);
  userCache[id] = user; // 전역 객체에 계속 추가
  // ⚠️ userCache는 절대 비워지지 않음!
}

// 사용자가 100명, 1000명, 10000명...
// Heap 메모리가 계속 증가 📈

예를 들어:

  • 어떤 객체를 전역 변수에 저장해두고
  • 더 이상 사용하지 않지만
  • 참조를 끊지 않은 경우

이 객체는 Heap에 계속 남아 있게 된다.

Node.js 서버처럼 프로세스가 오래 실행되는 환경에서는 이런 객체들이 쌓이면 메모리 사용량이 점점 증가하게 된다.

🧠 서버 메모리 사용량 추이

시작:  [████░░░░░░] 40%
1시간: [██████░░░░] 60%
2시간: [████████░░] 80%
3시간: [██████████] 100% 💥 서버 다운!

이것이 흔히 말하는 메모리 누수(memory leak) 이다.


🎯 왜 이 구조가 중요한가?

Call StackHeap 구조를 이해하면 다음 개념들이 자연스럽게 연결된다:

개념메모리와의 관계이유
이벤트 루프Call Stack과 Callback Queue의 관계Stack이 비어야 다음 작업 실행
비동기 처리setTimeout이 Stack에서 빠져나가는가비동기 작업은 별도 공간에서 대기
서버 성능 저하Heap 메모리가 부족하면 발생GC가 자주 실행되어 CPU 점유
메모리 누수사용하지 않는 객체가 Heap에 남아있는 경우참조가 끊기지 않아 GC 불가

특히 Node.js 환경에서는 프로세스가 계속 살아 있기 때문에 Heap 관리가 서버 성능에 직접적인 영향을 준다.


🤔 스스로에게 던져볼 질문

이 글을 읽고 나서 다음 질문에 답할 수 있다면 메모리 구조는 제대로 이해한 것이다:

Q1. 함수 호출 순서는 누가 관리할까?

A. Call Stack이 LIFO 방식으로 관리한다.
함수가 호출되면 Stack에 쌓이고, 실행이 끝나면 맨 위에서부터 제거된다.

Q2. 객체 데이터는 어디에 저장될까?

A. Heap 영역에 저장된다.
Call Stack에는 객체의 참조(메모리 주소)만 저장되고, 실제 데이터는 Heap에 있다.

Q3. Stack Overflow는 왜 발생할까?

A. 재귀 함수가 종료되지 않거나 함수 호출이 너무 깊게 중첩되면
Call Stack의 크기 제한을 초과하여 발생한다.

Q4. Heap에 남은 데이터는 언제 사라질까?

A. 더 이상 어떤 변수도 그 데이터를 참조하지 않을 때
Garbage Collection의 대상이 되어 자동으로 제거된다.


🎓 정리

✅ 핵심 요약

  • Call Stack함수 실행 흐름을 LIFO 방식으로 관리한다
  • Heap객체와 데이터 자체를 자유롭게 저장한다
  • JavaScript 실행은 Call StackHeap의 협력으로 이루어진다
  • 이 구조는 Node.js처럼 오래 실행되는 환경에서 메모리 관리가 왜 중요한지를 설명해준다

📊 최종 다이어그램

🧠 JavaScript 메모리 구조

┌─────────────────────────────────────────────────┐
│               JavaScript 실행 환경               │
├──────────────────────┬──────────────────────────┤
│   📚 Call Stack     │       📦 Heap            │
   (함수 호출 관리)          (데이터 저장)         │
├──────────────────────┼──────────────────────────┤
│  [함수3] ← 실행 중   │  0x1234: { name: "Kim" }  │
│  [함수2]0x5678: [1, 2, 3]       │
│  [함수1]0x9abc: { age: 30 }     │
│  [전역]...                     │
├──────────────────────┴──────────────────────────┤
│              🗑️ Garbage Collector              │
             (참조 없는 데이터 자동 제거)           │
└─────────────────────────────────────────────────┘
            ↓                       ↓
         실행 순서               데이터 저장

👉 다음 글 예고

다음 글에서는 이 메모리 구조를 가진 JavaScript 코드가 어떤 단위로 실행되고 관리되는지, 즉 프로세스에 대해 이야기해볼 예정이다.

  • 프로세스는 무엇이고 스레드와는 어떻게 다른가?
  • Node.js는 왜 싱글 스레드라고 하는가?
  • 그렇다면 Node.js는 어떻게 동시에 여러 작업을 처리할 수 있는가?
profile
눈코딩 재밌게 합시다!

0개의 댓글