Files
2nd/10_Wiki/Topics/Frontend/Memory-Leak-Debugging-in-JavaScript.md
T
koriweb d8a80f6272 chore(wiki): dangling 링크 canonical 정규화 (768파일/1200건)
이름만 다른(표기 변형) [[위키링크]]를 대상 문서의 canonical 제목으로 치환해
끊겼던 1,200개 링크를 연결. 제목/파일명 정규화 일치만 적용하고 별칭 매칭은
과병합 위험으로 제외(애매성 가드). 원본은 _link_reconcile_backup/ 에 백업.
도구: Datacollect/scripts/link_reconcile_apply.mjs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:24:15 +09:00

210 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: wiki-2026-0508-memory-leak-debugging-in-javascr
title: Memory Leak Debugging in JavaScript
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [JS Memory Leak, Heap Leak Debugging]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [javascript, performance, debugging, memory]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: JavaScript
framework: Chrome DevTools
---
# Memory Leak Debugging in JavaScript
## 매 한 줄
> **"매 unintended retention — 매 GC 매 reach 가능한 reference chain 매 끊지 못해 매 heap 매 grows unbounded"**. JS 매 mark-and-sweep GC 자동이지만 매 closure/listener/global/timer 매 long-lived reference 매 object lifecycle 매 의도와 분리시키면 매 leak 발생, 매 Chrome DevTools Heap Snapshot 매 진단 standard.
## 매 핵심
### 매 leak sources (top 5)
- **Detached DOM nodes**: 매 element removed from tree 매 JS reference 잔존.
- **Event listeners**: 매 addEventListener 매 removeEventListener 없이 매 component unmount.
- **Timers**: setInterval/setTimeout 매 cleanup 누락 매 closure 매 모두 retain.
- **Closures**: outer scope variables 매 inner function 매 capture 후 매 long-lived.
- **Global accumulation**: window/globalThis 매 cache/array 매 unbounded push.
### 매 detection tools
- **Chrome DevTools Memory**: Heap snapshot, allocation timeline, allocation sampling.
- **performance.measureUserAgentSpecificMemory()** (Chrome 89+): 매 cross-origin isolated context.
- **Node.js**: --inspect + Chrome DevTools, heapdump module, --heap-prof flag.
- **WeakRef + FinalizationRegistry**: 매 GC 관찰 (debugging only).
### 매 응용
1. SPA 매 route navigation 매 retain leak 진단.
2. Long-running dashboard 매 hour-scale leak 감시.
3. Node.js server 매 RSS growth 매 root cause.
4. React/Vue component lifecycle leak detection.
## 💻 패턴
### Heap snapshot 3-snapshot technique
```
1. App 초기 load → Snapshot 1 (baseline)
2. Suspect action 수행 (modal open/close ×10) → Snapshot 2
3. 동일 action 재수행 → Snapshot 3
4. Snapshot 3 의 Comparison → Snapshot 1
5. "Allocated between snapshots 1 and 3" 의 still-alive objects = leak
```
### Detached DOM 탐색 (DevTools Console)
```js
// Heap snapshot Class filter:
// "Detached HTMLDivElement"
// "Detached HTMLElement"
// 매 instance 매 retainer chain 매 inspect — 매 root retainer 매 leak 출처
```
### Event listener leak — fix pattern
```js
// 매 BAD
class Widget {
constructor() {
window.addEventListener('resize', this.onResize.bind(this));
}
onResize() { /* ... */ }
destroy() { /* listener still attached */ }
}
// 매 GOOD
class Widget {
constructor() {
this.onResize = this.onResize.bind(this);
window.addEventListener('resize', this.onResize);
}
onResize() { /* ... */ }
destroy() {
window.removeEventListener('resize', this.onResize);
}
}
```
### AbortController 매 modern cleanup
```js
class Component {
constructor() {
this.ac = new AbortController();
const { signal } = this.ac;
window.addEventListener('scroll', this.onScroll, { signal });
window.addEventListener('resize', this.onResize, { signal });
fetch('/api', { signal });
}
destroy() {
this.ac.abort(); // 매 모든 listener + fetch 매 한 번에 cleanup
}
}
```
### Timer leak fix
```js
// 매 BAD — closure captures large data
function startPolling(bigData) {
setInterval(() => {
console.log(bigData.length); // bigData retained forever
}, 1000);
}
// 매 GOOD — explicit handle + cleanup
const handle = setInterval(poll, 1000);
function stop() { clearInterval(handle); }
```
### WeakMap 매 cache without leak
```js
// 매 BAD — Map 매 key 매 GC X
const cache = new Map();
function getMeta(node) {
if (!cache.has(node)) cache.set(node, computeMeta(node));
return cache.get(node); // node removed from DOM but still in cache
}
// 매 GOOD — WeakMap key 매 GC 가능
const cache = new WeakMap();
function getMeta(node) {
if (!cache.has(node)) cache.set(node, computeMeta(node));
return cache.get(node);
}
```
### performance.measureUserAgentSpecificMemory
```js
// crossOriginIsolated context (COOP+COEP headers) 필요
if (crossOriginIsolated && performance.measureUserAgentSpecificMemory) {
const result = await performance.measureUserAgentSpecificMemory();
console.log('bytes:', result.bytes);
console.table(result.breakdown);
}
```
### FinalizationRegistry 매 GC 관찰 (debug only)
```js
const registry = new FinalizationRegistry((tag) => {
console.log(`GC'd: ${tag}`);
});
class Tracked {
constructor(name) {
registry.register(this, name);
}
}
new Tracked('widget-1'); // → "GC'd: widget-1" eventually (or never)
```
### Node.js heap snapshot
```bash
node --inspect server.js
# 매 chrome://inspect → Memory → Take heap snapshot
# 또는 programmatic:
```
```js
import { writeHeapSnapshot } from 'node:v8';
const path = writeHeapSnapshot(); // .heapsnapshot file
console.log(`Snapshot: ${path}`);
```
## 매 결정 기준
| 상황 | Tool/Approach |
|---|---|
| Browser SPA growing memory | DevTools Heap Snapshot 3-snapshot |
| 매 frame allocation hotspot | Allocation timeline (sampling) |
| Detached DOM 의심 | Class filter "Detached " in snapshot |
| Node.js RSS growth | writeHeapSnapshot + Chrome DevTools |
| Continuous monitoring (production) | performance.measureUserAgentSpecificMemory |
| Event listener leak | AbortController 매 unified cleanup |
**기본값**: 매 Heap Snapshot 3-snapshot diff — 매 retainer chain 매 따라 root 매 식별.
## 🔗 Graph
- 부모: [[Garbage Collection]]
- Adjacent: [[Chrome DevTools]] · [[AbortController]]
## 🤖 LLM 활용
**언제**: 매 SPA/long-running app 의 메모리 증가, 매 unmount 후 referent 잔존, 매 production memory metrics 의 anomaly.
**언제 X**: 매 short-lived script (CLI tool), 매 GC pause 문제 (different — GC tuning territory).
## ❌ 안티패턴
- **`delete` keyword 의존**: 매 reference 매 nullify 안 함 — 매 다른 reference 매 retain.
- **`window.gc()` 매 production**: 매 only with --expose-gc flag, 매 hint 일 뿐.
- **Allocation timeline 매 production trace**: 매 overhead 매 큼 — 매 staging 에서.
- **One-snapshot 진단**: 매 baseline 없으면 매 noise 와 leak 매 구분 불가.
- **DevTools 매 incognito 가정**: 매 extension 매 heap pollution — 매 incognito + 매 disabled extensions.
## 🧪 검증 / 중복
- Verified (Chrome DevTools docs, V8 blog, Node.js v8 module).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — leak source taxonomy + DevTools workflow + AbortController/WeakMap patterns |