Files
2nd/10_Wiki/Topics/AI_and_ML/브라우저 메모리 누수 탐지(Browser Memory Leak Detection).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

6.5 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-브라우저-메모리-누수-탐지-browser-memory-le 브라우저 메모리 누수 탐지(Browser Memory Leak Detection) 10_Wiki/Topics verified self
Memory Leak Detection
Browser Memory Profiling
JS Memory Leak
none A 0.9 applied
browser
memory-leak
performance
devtools
javascript
2026-05-10 pending
language framework
JavaScript Chrome DevTools

브라우저 메모리 누수 탐지(Browser Memory Leak Detection)

매 한 줄

"매 SPA 가 시간이 지날수록 느려진다면 90% memory leak". JS 의 GC 는 매 reachable object 만 살리지만, detached DOM, forgotten timer, closure capture, global accumulation 이 매 unintended retention 을 만든다. 매 Chrome DevTools Memory panel + Performance panel 의 heap snapshot diff + allocation timeline 이 매 standard tool.

매 핵심

매 4 가지 흔한 leak pattern

  • Detached DOM: 매 element 를 removeChild 했지만 JS reference 가 살아있음 → DOM tree + element subtree 통째로 retained.
  • Forgotten timer / listener: setInterval / addEventListener 의 callback closure 가 매 component scope 를 hold.
  • Closure over big data: outer function 의 large array 를 inner closure 가 무의식적으로 capture.
  • Global accumulation: window.cache[key] = ... 식의 무한 적재.

매 detection workflow (3 snapshot technique)

  1. App load → snapshot A.
  2. 매 leak-suspect action 100 회 (open/close modal …) → snapshot B.
  3. 매 동일 action 100 회 더 → snapshot C.
  4. Comparison: B → C 사이 Delta 가 0 이어야 정상. 매 keep growing → leak.

매 응용

  1. SPA debugging (React/Vue route transition leak).
  2. Memory regression CI (puppeteer + heap snapshot).
  3. Production monitoring (performance.memory).

💻 패턴

Detect detached DOM (DevTools console)

// 매 Memory panel → "Detached" filter 로 search
// 또는 console 에서:
function findDetached() {
  const all = $$('*'); // visible
  // queryObjects(HTMLElement) — DevTools console 전용
  console.log('visible:', all.length);
}
queryObjects(HTMLElement); // 매 DevTools console only

Cleanup pattern (idiomatic)

// React useEffect cleanup
useEffect(() => {
  const id = setInterval(tick, 1000);
  const onResize = () => recalc();
  window.addEventListener('resize', onResize);
  return () => {
    clearInterval(id);
    window.removeEventListener('resize', onResize);
  };
}, []);

WeakMap / WeakRef for caches

// 매 strong ref → leak. WeakMap → key GC 되면 entry 자동 사라짐
const meta = new WeakMap();
function attach(node, info) { meta.set(node, info); }
// node 가 detached 되고 reference 끊기면 entry GC

// WeakRef + FinalizationRegistry (2021+)
const reg = new FinalizationRegistry(id => console.log('GC:', id));
const w = new WeakRef(obj);
reg.register(obj, 'my-resource');

performance.memory monitoring (Chrome only)

function logMemory() {
  const m = performance.memory;
  console.log({
    used:   (m.usedJSHeapSize  / 1048576).toFixed(1) + 'MB',
    total:  (m.totalJSHeapSize / 1048576).toFixed(1) + 'MB',
    limit:  (m.jsHeapSizeLimit / 1048576).toFixed(1) + 'MB',
  });
}
setInterval(logMemory, 5000);

3-snapshot diff (puppeteer CI)

import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:3000');

const session = await page.target().createCDPSession();
async function snapshot() {
  await session.send('HeapProfiler.collectGarbage');
  const m = await page.evaluate(() => performance.memory.usedJSHeapSize);
  return m;
}

const a = await snapshot();
for (let i = 0; i < 100; i++) await page.click('#open-modal'), await page.click('#close-modal');
const b = await snapshot();
for (let i = 0; i < 100; i++) await page.click('#open-modal'), await page.click('#close-modal');
const c = await snapshot();

const growth = c - b;
if (growth > 1_000_000) throw new Error(`Memory leak: +${growth} bytes between B→C`);

AbortController for fetch / event cleanup

const ac = new AbortController();
window.addEventListener('scroll', onScroll, { signal: ac.signal });
fetch(url, { signal: ac.signal });
// component unmount:
ac.abort(); // 매 listener + fetch 동시 cleanup

Common React leak — stale closure capture

// 매 BAD — bigData 를 closure 가 잡음, unmount 후에도 retain
useEffect(() => {
  const bigData = computeMassive();
  const id = setTimeout(() => console.log(bigData[0]), 60_000);
  // cleanup 없음 → leak
}, []);

// 매 GOOD
useEffect(() => {
  const bigData = computeMassive();
  const id = setTimeout(() => console.log(bigData[0]), 60_000);
  return () => clearTimeout(id);
}, []);

매 결정 기준

상황 Tool
ad-hoc 탐색 DevTools Memory → Heap snapshot
allocation 출처 추적 Performance → record + Memory checkbox
detached DOM Memory → "Detached" filter
CI regression puppeteer + HeapProfiler.collectGarbage + performance.memory
production monitoring performance.memory polling → telemetry

기본값: 매 3-snapshot heap diff (A=load, B=after-100-actions, C=after-200-actions). B→C 0 growth 가 합격선.

🔗 Graph

🤖 LLM 활용

언제: SPA 가 time-on-page 로 점점 느려질 때, mobile OOM, infinite scroll page 의 retention. 언제 X: server-side memory leak (그건 Node heap profiler 영역).

안티패턴

  • GC 강제 호출 시도: 매 gc() 는 standard 아님. DevTools "Collect garbage" 버튼이 유일.
  • performance.memory 만 보고 판단: 매 Chromium 전용, Firefox/Safari 없음. heap snapshot 이 ground truth.
  • removeChild 만 하고 reference cleanup 안 함: 매 detached DOM leak 의 source.
  • setInterval 매 component mount 마다 + cleanup 없음: 매 SPA killer.

🧪 검증 / 중복

  • Verified (Chrome DevTools docs, web.dev memory profiling, V8 blog).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — leak patterns + 3-snapshot diff + WeakRef/AbortController patterns