1. 메모리 누수란 무엇인가
메모리 누수(Memory Leak)는
더 이상 필요하지 않은 객체가 여전히 참조되고 있어
GC(가비지 컬렉션)에 의해 회수되지 못하는 현상을 의미한다.
즉, “쓰레기가 쌓이는데 버리지 못하는 상황”이다.
🧠 예시
- 페이지를 열어둔 채 시간이 지날수록 브라우저가 느려짐
- SPA 앱에서 라우팅 이동 후에도 이전 페이지 데이터가 남아 있음
- setInterval, Event Listener가 해제되지 않음
2. 브라우저의 메모리 구조 다시보기
브라우저의 JS 엔진(V8 등)은 크게 두 가지 영역으로 메모리를 관리한다 👇
영역 설명
| Stack | 함수 호출, 기본형 데이터 저장 (Number, Boolean 등) |
| Heap | 객체, 배열, 함수 등 동적 데이터 저장 |
function demo() {
const x = 10; // Stack
const obj = { y: 20 }; // Heap
}
✅ Stack은 짧은 생명 주기를 가지며 자동으로 해제
❌ Heap은 객체 참조가 남아 있으면 GC가 제거하지 못함
3. 가비지 컬렉션(Garbage Collection)의 원리
자바스크립트는 자동으로 메모리를 관리하지만,
“참조가 남아 있는지” 를 기준으로 객체를 제거한다.
🔹 Mark-and-Sweep 알고리즘
1️⃣ Root(전역 객체)에서 접근 가능한 객체를 Mark(표시)
2️⃣ 표시되지 않은 객체를 Sweep(제거)
let obj = { name: "test" };
obj = null; // 참조 해제 → GC 가능
✅ 참조가 끊긴 순간 다음 GC 사이클에서 정리됨
4. 메모리 누수가 생기는 대표적인 패턴 5가지
(1) 전역 변수 남용
전역 변수는 앱이 종료될 때까지 메모리에서 해제되지 않는다.
var data = []; // ❌ 전역 변수
setInterval(() => data.push(new Array(1000).fill('*')), 1000);
➡ 시간이 갈수록 data가 계속 쌓임 → 브라우저 메모리 폭발
✅ 해결: 전역 대신 함수 스코프 또는 클로저 내부에서 관리
(2) 클로저(Closure) 오용
클로저는 내부 함수가 외부 변수에 접근할 수 있도록 하지만,
필요 없는 참조까지 유지하면 메모리 누수가 발생한다.
function outer() {
const largeArray = new Array(100000).fill('*');
return function inner() {
console.log(largeArray[0]); // 참조 유지됨
};
}
const leak = outer(); // largeArray는 해제되지 않음
✅ 해결: 필요 없는 클로저는 null로 끊기
leak = null;
(3) 이벤트 리스너 해제 누락
DOM 요소를 제거했는데 이벤트 리스너가 남아 있으면,
해당 함수 참조가 유지되어 GC가 회수하지 못한다.
const button = document.getElementById("btn");
button.addEventListener("click", () => console.log("clicked"));
document.body.removeChild(button); // ❌ 리스너는 여전히 참조됨
✅ 해결: 이벤트 제거 필수
button.removeEventListener("click", handleClick);
(4) setInterval, setTimeout 미정리
setInterval(() => {
console.log("running...");
}, 1000);
이 타이머가 중단되지 않으면,
콜백이 계속 메모리에 남고 클로저를 통해 객체를 계속 참조한다.
✅ 해결: 페이지 이동 전 반드시 clear
clearInterval(timer);
(5) DOM 참조를 변수로 계속 유지
DOM을 삭제해도 JS 변수에 참조가 남아 있으면
GC는 해당 노드를 지우지 않는다.
let el = document.getElementById("app");
document.body.removeChild(el); // ❌ 여전히 el 참조 존재
✅ 해결
el = null;
5. 실무에서 자주 터지는 메모리 누수 패턴
상황 원인 해결 방법
| SPA 페이지 전환 후 느려짐 | 이전 라우트 컴포넌트가 해제되지 않음 | useEffect cleanup, beforeUnmount 사용 |
| 무한 Scroll 구현 | 이벤트 핸들러 누적 | removeEventListener 또는 AbortController |
| setTimeout 반복 호출 | 클로저 참조 유지 | clearTimeout() 호출 |
| React 상태관리 | 불필요한 state 유지 | unmount 시 state reset |
6. React 기반 메모리 누수 예시
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// ✅ cleanup 함수 필수
return () => clearInterval(interval);
}, []);
✅ useEffect의 cleanup 함수는 GC보다 먼저 실행되어
참조를 끊고 메모리를 확보하게 돕는다.
7. 메모리 누수 탐지 방법
브라우저 개발자 도구(DevTools)에서 쉽게 추적 가능하다.
(1) Performance 탭
- Memory Profile을 측정
- 점점 증가하는 메모리 그래프 확인
(2) Memory 탭
- “Heap Snapshot” 촬영
- 특정 객체가 GC 후에도 남아 있는지 추적
✅ 오래된 리스너, 클로저, 캐시 객체를 쉽게 식별 가능
8. 실무 방어 전략
전략 설명
| 🔹 이벤트 해제 습관화 | addEventListener 후 반드시 removeEventListener |
| 🔹 setInterval/Timeout 정리 | 컴포넌트 unmount 시 반드시 clear |
| 🔹 클로저 최소화 | 불필요한 외부 변수 참조 지양 |
| 🔹 WeakMap / WeakSet 사용 | 참조가 사라지면 자동으로 GC 가능 |
| 🔹 React cleanup 활용 | useEffect 리턴 함수 적극 활용 |
예시 👇
const cache = new WeakMap();
function remember(el, data) {
cache.set(el, data);
}
✅ WeakMap은 key가 GC될 때 자동으로 해제된다.
9. 메모리 누수 방지 코드 패턴
class Resource {
constructor() {
this.timer = setInterval(() => console.log('running'), 1000);
window.addEventListener('resize', this.handleResize);
}
destroy() {
clearInterval(this.timer);
window.removeEventListener('resize', this.handleResize);
}
handleResize() {
console.log('resized');
}
}
const r = new Resource();
// ✅ 종료 시 반드시 정리
r.destroy();
10. 마무리
메모리 누수는 단순한 버그가 아니라,
시간이 지날수록 시스템을 죽이는 병이다.
GC가 있다고 해서 모든 걸 자동으로 해결해주지 않는다.
“가비지 컬렉션은 청소부일 뿐, 청소 시점을 아는 건 개발자 자신이다.”
클로저, 이벤트, 타이머, DOM 참조 —
이 네 가지를 관리하는 습관만 들여도,
SPA나 대형 프론트엔드 앱의 성능 저하는 대부분 사라진다.
'frontend > javascript' 카테고리의 다른 글
| 🟨 2-28. 네트워크 지연 없는 프론트엔드 — Fetch, Caching, Preload 완전 정복 (0) | 2025.11.07 |
|---|---|
| 🟨 2-27. 비동기 코드의 병목 해결 — Event Loop, Promise, Web API의 완전한 협력 구조 (0) | 2025.11.07 |
| 🟨 2-25. 자바스크립트 엔진 내부 구조 — V8 엔진이 코드를 처리하는 진짜 방식 (0) | 2025.11.07 |
| 🟨 2-24. 렌더링 최적화 실무 전략 — Layout, Paint, Composite 성능 개선법 총정리 (0) | 2025.11.07 |
| 🟨 2-23. 브라우저 렌더링 과정 — 자바스크립트, CSS, DOM이 함께 그려지는 진짜 순서 (0) | 2025.11.07 |