JavaScript
호출 스택과 도달 가능성 기반 메모리 관리
함수 실행 순서는 호출 스택으로 이해하고, 메모리에 남은 객체가 언제 회수되는지는 루트로부터의 도달 가능성(Reachability)과 참조 수명으로 분석합니다.
이럴 때 필요해요
오래 켜 두는 웹 애플리케이션에서 이벤트 리스너·타이머·캐시가 불필요한 메모리를 계속 붙잡고 있는 누수 원인을 찾을 수 있습니다.
어떻게 동작하나요?
함수가 다른 함수를 부르고 그 함수가 끝난 뒤 원래 자리로 돌아오는 실행 흐름은 호출 스택(Call Stack)이라는 후입선출(LIFO) 구조로 설명할 수 있습니다. 함수가 호출되면 스택에 실행 프레임이 쌓이고, 반환되면 위에서부터 제거됩니다. 반면 함수 호출이 끝난 뒤에도 여러 곳에서 계속 참조할 수 있는 객체나 배열, 클로저 데이터는 힙(Heap) 메모리 모델로 설명합니다. 다만 '원시값은 무조건 물리적 스택, 객체는 무조건 힙'이라는 이분법은 이해를 돕는 개념 모델일 뿐 실제 엔진은 상황에 따라 최적화를 수행합니다.
그렇다면 힙에 만들어진 객체는 언제 메모리에서 정리될까요? JavaScript의 가비지 컬렉터(GC)는 전역 객체나 현재 실행 중인 스택 프레임 같은 '루트(Root)'에서 시작해 참조 사슬을 따라가며 도달할 수 있는(Reachable) 객체만 살려 두고, 어느 경로로도 도달할 수 없게 된 객체를 수거합니다. 따라서 두 객체가 서로를 가리키는 순환 참조가 있더라도 루트에서 그 그룹으로 가는 연결만 끊어지면 정상적으로 회수됩니다.
메모리 누수(Memory Leak)는 더 이상 화면이나 로직에서 쓰지 않는 데이터를 도달 가능한 경로에 실수로 남겨 두었을 때 발생합니다. 예를 들어 화면에서 제거한 DOM 노드를 전역 배열에 담아 두거나, 컴포넌트가 사라졌는데도 clearInterval이나 removeEventListener를 호출하지 않아 타이머·리스너의 클로저가 큰 데이터를 계속 붙잡고 있는 경우가 대표적입니다. 따라서 자원을 등록할 때는 정리(cleanup) 함수를 함께 짝지어 두고, 누수가 의심될 때는 힙 스냅샷을 여러 번 찍어 회수되지 않고 누적되는 객체의 보유 경로(Retaining Path)를 추적합니다.
코드 예제
동작을 설명하는 읽기 전용 예제입니다.
function start() {
const data = new Array(1000).fill('item');
const timer = setInterval(() => console.log(data.length), 1000);
return () => clearInterval(timer);
}
const stop = start();
stop();타이머 콜백이 클로저를 통해 큰 배열 data를 참조하고 있으므로, 작업이 끝났을 때 반환된 stop()을 호출해 타이머를 해제해야 data도 함께 회수될 수 있습니다.
자주 헷갈리는 포인트
- 애플리케이션 코드에서 가비지 컬렉션이 실행되는 정확한 시점을 강제하거나 예측할 수 있다고 가정하지 않습니다.
- 함수 실행 순서를 관리하는 호출 스택/메모리 힙과 자료구조 수업에서 다루는 우선순위 큐용 힙(Heap Tree)을 같은 것으로 혼동하지 않습니다.
내 말로 설명해 보기
화면(DOM 트리)에서 이미 제거한 요소가 가비지 컬렉션되지 않고 메모리에 계속 남아 있다면 어떤 참조 경로를 먼저 의심해 봐야 할까요?
함께 읽으면 좋은 개념
관련 면접 질문 2개
가비지 컬렉션과 메모리 누수는 어떻게 설명하나요?
JavaScript 엔진은 루트(전역 객체, 현재 호출 스택 등)에서 시작해 참조를 따라 도달할 수 있는 객체는 살려 두고, 더 이상 도달할 수 없는 객체를 가비지 컬렉터(GC)가 자동으로 회수합니다. 하지만 더 이상 쓰지 않는 데이터라도 전역 변수, 해제하지 않은 타이머·이벤트 리스너, 클로저나 캐시에 참조가 남아 있으면 GC가 수거하지 못해 메모리 누수가 됩니다. 따라서 작업 종료 시 리스너와 타이머를 정리하고, 힙 스냅샷의 보유 경로(Retaining Path)로 누수 원인을 찾습니다.
꼬리질문과 참고자료 →호출 스택과 힙은 각각 무엇을 설명하나요?
호출 스택(Call Stack)은 현재 실행 중인 함수들의 호출 순서와 복귀 지점을 후입선출(LIFO) 구조로 관리하는 모델이고, 힙(Heap)은 객체나 배열, 클로저 환경처럼 크기가 동적으로 변하고 함수 호출이 끝난 뒤에도 살아남을 수 있는 데이터가 저장되는 메모리 영역을 설명하는 모델입니다. 재귀 호출이 종료 조건 없이 깊어지면 호출 스택 한도를 넘어 RangeError(Maximum call stack size exceeded)가 발생하고, 힙에 할당된 객체의 참조를 계속 붙잡고 있으면 메모리 사용량이 늘어납니다.
꼬리질문과 참고자료 →