Як профілювати використання пам'яті?
У браузері (Chrome DevTools)
1) Базовий сценарій "чи росте heap?"
- Відкрий DevTools -> Memory.
- Вибери Heap snapshot -> Take snapshot (знімок №1).
- Виконай підозрілі дії (навігація по SPA, відкриття/закриття модалок, списків тощо).
- Take snapshot знову (знімок №2).
- Порівняй: вкладка Comparison -> відсортуй за Δ Size/Δ Count. Шукай "зростаючих" власників (classes/конструктори), Detached DOM nodes і великі масиви/Map.
Що дивитися:
- Retainers (ланцюжки утримання) - покажуть, яке посилання заважає GC.
- Distance - мале значення означає близько до "root"; такі об'єкти рідко звільняються.
2) Реал-тайм "де створюються об'єкти"
Allocation instrumentation on timeline:
- Memory -> Allocation instrumentation on timeline -> Start.
- Відтвори сценарій 10-30 сек.
- Stop -> клікай по піках графіка -> дивись Allocation stacks (рядки коду, де відбуваються аллокації).
Плюси: показує гарячі точки виділень "за місцем". Мінуси: шумно, але чудово для ловлі сплесків.
3) Легкий режим
Sampling allocation profiler - менш точний, але майже без оверхеду. Зручний, коли складно довго відтворювати баг.
4) Таймлайн пам'яті
У вкладці Performance увімкни метрику Memory -> зроби запис. Якщо після "ручного GC" (іконка смітника) heap не повертається приблизно до вихідного рівня, є витік.
5) Швидкі перевірки з консолі
// Поточний розмір хипа (лише Chromium):
performance.memory.usedJSHeapSize
// (Експериментально) детальніше за агентами:
if (performance.measureUserAgentSpecificMemory) {
performance.measureUserAgentSpecificMemory().then(console.log);
}У Node.js
1) Інспекція через DevTools
Запусти з інспектором і відкрий у Chrome:
node --inspect index.js
# або при старті дебагу
node --inspect-brk index.jsУ Chrome -> chrome://inspect -> Open dedicated DevTools -> вкладка Memory -> знімки heap, як у браузері.
2) Знімок хипа в рантаймі
npm i heapdumpimport heapdump from 'heapdump';
setInterval(() => {
heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`);
}, 60_000);Відкрий .heapsnapshot у DevTools і порівняй.
3) Швидкі лічильники
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-трейси (діагностика)
node --trace-gc index.jsДивись частоту і тривалість пауз; постійне зростання "after GC" - поганий знак.
Для SPA/React (і взагалі компонентів)
1) Негласний стандартний протокол перевірки
- Відкрий сторінку А -> зроби Heap snapshot #1.
- Перейди на сторінку B, повернися на А (або відкрий/закрий компонент) -> #2.
- Натисни GC (іконка смітника) -> #3.
- Порівняй #1 і #3. Якщо об'єкти сторінки А/компонента залишилися, а їх кількість/розмір ростуть після повторів, це витік.
Шукай:
- Detached DOM nodes;
- обробники
EventListenerбезremoveEventListener; - активні
Timeout/Interval; - "зомбі"-підписки (WebSocket, RxJS);
- кеші (
Map/Set) без TTL/очищення; - великі структури в Context/Redux, які ніхто не скидає.
2) Специфіка React
-
Перевіряй
useEffectна cleanup (повернення функції):javascriptuseEffect(() => { const id = setInterval(fetchData, 5000); return () => clearInterval(id); // обов'язково! }, []); -
React DevTools Profiler: компонент має Unmount-итися при відході зі сторінки.
-
Стеж за
refна DOM: після unmount вони мають статиnull.
Методика пошуку витоку (коротко)
- Зафіксуй симптом: "через N хвилин heap росте з X до Y".
- Звузь сценарій: мінімально відтворювана послідовність дій.
- Збери дані: Heap snapshot до/після, Allocation timeline, Perf Memory.
- Знайди власника: клас/конструктор, де відбувається зростання -> дивись Retainers.
- Зв'яжи з кодом: рядок/файл через Allocation stacks або sourcemaps.
- Виправ: додай cleanup/видали посилання/обмеж кеш.
- Перевір: повтори кроки 1-3 - зростання має зникнути.
- Автоматизуй (за можливості): e2e-скрипт, який вимірює
usedJSHeapSizeпо кроках.
Часті "підказки" у звітах
- Detached HTMLDivElement росте -> забули зняти слухача/посилання на DOM.
- Ростуть масиви/об'єкти в Map/Set -> немає TTL/LRU-очищення.
- Багато Timeout/Interval -> немає
clearTimeout/clearInterval. - Купа екземплярів компонента після навігації -> не розмонтовується або "тримають" замикання/підписки.
- Витік у third-party бібліотеці -> обгорни у свій адаптер з cleanup або заміни.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.