Skip to main content

Як профілювати використання пам'яті?

У браузері (Chrome DevTools)

1) Базовий сценарій "чи росте heap?"

  1. Відкрий DevTools -> Memory.
  2. Вибери Heap snapshot -> Take snapshot (знімок №1).
  3. Виконай підозрілі дії (навігація по SPA, відкриття/закриття модалок, списків тощо).
  4. Take snapshot знову (знімок №2).
  5. Порівняй: вкладка Comparison -> відсортуй за Δ Size/Δ Count. Шукай "зростаючих" власників (classes/конструктори), Detached DOM nodes і великі масиви/Map.

Що дивитися:

  • Retainers (ланцюжки утримання) - покажуть, яке посилання заважає GC.
  • Distance - мале значення означає близько до "root"; такі об'єкти рідко звільняються.

2) Реал-тайм "де створюються об'єкти"

Allocation instrumentation on timeline:

  1. Memory -> Allocation instrumentation on timeline -> Start.
  2. Відтвори сценарій 10-30 сек.
  3. Stop -> клікай по піках графіка -> дивись Allocation stacks (рядки коду, де відбуваються аллокації).

Плюси: показує гарячі точки виділень "за місцем". Мінуси: шумно, але чудово для ловлі сплесків.

3) Легкий режим

Sampling allocation profiler - менш точний, але майже без оверхеду. Зручний, коли складно довго відтворювати баг.

4) Таймлайн пам'яті

У вкладці Performance увімкни метрику Memory -> зроби запис. Якщо після "ручного GC" (іконка смітника) heap не повертається приблизно до вихідного рівня, є витік.

5) Швидкі перевірки з консолі

javascript
// Поточний розмір хипа (лише Chromium): performance.memory.usedJSHeapSize // (Експериментально) детальніше за агентами: if (performance.measureUserAgentSpecificMemory) { performance.measureUserAgentSpecificMemory().then(console.log); }

У Node.js

1) Інспекція через DevTools

Запусти з інспектором і відкрий у Chrome:

javascript
node --inspect index.js # або при старті дебагу node --inspect-brk index.js

У Chrome -> chrome://inspect -> Open dedicated DevTools -> вкладка Memory -> знімки heap, як у браузері.

2) Знімок хипа в рантаймі

javascript
npm i heapdump
javascript
import heapdump from 'heapdump'; setInterval(() => { heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`); }, 60_000);

Відкрий .heapsnapshot у DevTools і порівняй.

3) Швидкі лічильники

javascript
setInterval(() => { const m = process.memoryUsage(); console.log({ rss: Math.round(m.rss/1024/1024)+'MB', heapUsed: Math.round(m.heapUsed/1024/1024)+'MB', heapTotal: Math.round(m.heapTotal/1024/1024)+'MB', ext: Math.round(m.external/1024/1024)+'MB' }); }, 5000);

Якщо heapUsed пиляється вгору і не повертається, є підозра на витік.

4) GC-трейси (діагностика)

javascript
node --trace-gc index.js

Дивись частоту і тривалість пауз; постійне зростання "after GC" - поганий знак.

Для SPA/React (і взагалі компонентів)

1) Негласний стандартний протокол перевірки

  1. Відкрий сторінку А -> зроби Heap snapshot #1.
  2. Перейди на сторінку B, повернися на А (або відкрий/закрий компонент) -> #2.
  3. Натисни GC (іконка смітника) -> #3.
  4. Порівняй #1 і #3. Якщо об'єкти сторінки А/компонента залишилися, а їх кількість/розмір ростуть після повторів, це витік.

Шукай:

  • Detached DOM nodes;
  • обробники EventListener без removeEventListener;
  • активні Timeout/Interval;
  • "зомбі"-підписки (WebSocket, RxJS);
  • кеші (Map/Set) без TTL/очищення;
  • великі структури в Context/Redux, які ніхто не скидає.

2) Специфіка React

  • Перевіряй useEffect на cleanup (повернення функції):

    javascript
    useEffect(() => { const id = setInterval(fetchData, 5000); return () => clearInterval(id); // обов'язково! }, []);
  • React DevTools Profiler: компонент має Unmount-итися при відході зі сторінки.

  • Стеж за ref на DOM: після unmount вони мають стати null.

Методика пошуку витоку (коротко)

  1. Зафіксуй симптом: "через N хвилин heap росте з X до Y".
  2. Звузь сценарій: мінімально відтворювана послідовність дій.
  3. Збери дані: Heap snapshot до/після, Allocation timeline, Perf Memory.
  4. Знайди власника: клас/конструктор, де відбувається зростання -> дивись Retainers.
  5. Зв'яжи з кодом: рядок/файл через Allocation stacks або sourcemaps.
  6. Виправ: додай cleanup/видали посилання/обмеж кеш.
  7. Перевір: повтори кроки 1-3 - зростання має зникнути.
  8. Автоматизуй (за можливості): e2e-скрипт, який вимірює usedJSHeapSize по кроках.

Часті "підказки" у звітах

  • Detached HTMLDivElement росте -> забули зняти слухача/посилання на DOM.
  • Ростуть масиви/об'єкти в Map/Set -> немає TTL/LRU-очищення.
  • Багато Timeout/Interval -> немає clearTimeout/clearInterval.
  • Купа екземплярів компонента після навігації -> не розмонтовується або "тримають" замикання/підписки.
  • Витік у third-party бібліотеці -> обгорни у свій адаптер з cleanup або заміни.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.