Перейти к содержимому

Memory Profiling

Профилирование памяти через heap snapshots (Chrome DevTools / v8.writeHeapSnapshot) — обязательный навык Senior: ключевые понятия retained size vs shallow size и умение сравнивать два snapshot для нахождения утечки.

// Node.js: heap snapshot программно
const v8 = require('v8');
const path = require('path');
function takeSnapshot(label = '') {
const filename = path.join(
process.cwd(),
`heap-${label}-${Date.now()}.heapsnapshot`
);
v8.writeHeapSnapshot(filename);
console.log('Snapshot:', filename);
return filename;
}
// Открыть в Chrome DevTools: Memory → Load Profile
// Или: node --heap-snapshot-signal=SIGUSR2 app.js
// kill -USR2 <pid> ← снять снимок в рантайме без рестарта
// Retained size vs Shallow size
// Shallow size: память, занятая САМИМ объектом (поля, не референсы)
// Retained size: память, которая освободится при GC объекта
// = объект + всё, доступное ТОЛЬКО через него
// root → A → B (10 MB буфер)
// Shallow(A) = маленький (несколько полей)
// Retained(A) = большой (включает B и его 10 MB)
// Workflow для диагностики утечки:
// 1. Снять snapshot #1 (baseline)
// 2. Воспроизвести action N раз (login/logout, open/close)
// 3. Принудительный GC: если есть global.gc() → вызвать
// 4. Снять snapshot #2
// 5. Comparison view: Objects in #2 but not in #1 → кандидаты на утечку
// Типичные источники утечек:
// - Глобальные Map/Set без TTL или LRU eviction
// - Event listeners без removeEventListener
// - Closures, удерживающие DOM nodes или большие буферы
// - Timers (setInterval) без clearInterval
// process.memoryUsage() для мониторинга тренда
function monitorMemory(label) {
const m = process.memoryUsage();
console.log(`[${label}]`, {
heapUsed: (m.heapUsed / 1024 / 1024).toFixed(2) + ' MB',
heapTotal: (m.heapTotal / 1024 / 1024).toFixed(2) + ' MB',
rss: (m.rss / 1024 / 1024).toFixed(2) + ' MB',
external: (m.external / 1024 / 1024).toFixed(2) + ' MB',
// external: C++ объекты (Buffer, TypedArray) — вне V8 heap
});
}
// Демо: утечка через event listener
const EventEmitter = require('events');
const ee = new EventEmitter();
function leak() {
const bigData = Buffer.alloc(1024 * 1024); // 1 MB
ee.on('data', () => void bigData); // bigData удерживается listener-ом
}
for (let i = 0; i < 20; i++) leak(); // 20 MB утечки
monitorMemory('after leak');
// heapUsed + external вырастут на ~20 MB

Итог: Профилирование памяти требует понимания retained vs shallow size и умения сравнивать два heap snapshot; большинство утечек — незамеченные ссылки через event listeners, closures и глобальные кэши без eviction.