Что такое V8 heap snapshot и как его анализировать для поиска утечек памяти?
Что такое heap snapshot
Heap snapshot — это моментальный снимок всех объектов, находящихся в куче (heap) движка V8, вместе со ссылками между ними. Он показывает, сколько памяти занимает каждый объект, сколько экземпляров каждого типа существует и какие объекты удерживают друг друга (дерево retainers). Снапшот используется, чтобы найти объекты, которые не должны жить долго, но остаются в памяти из-за забытых ссылок — то есть найти утечку.
Как снять snapshot
- Chrome DevTools — запустить процесс с
node --inspectи открыть вкладку Memory вchrome://inspect. - Программно — через встроенный модуль
v8. - Сторонние пакеты —
heapdump,heap-profiler, которые позволяют снимать снапшот по сигналу прямо в production.
Как анализировать
Сравнение снапшотов (Comparison)
Самый надёжный способ поймать утечку — снять минимум три снапшота: до нагрузки, после нагрузки и ещё раз после принудительной сборки мусора и повторного цикла нагрузки. В DevTools во вкладке Memory выбирается режим Comparison, который показывает разницу между снимками: сколько объектов появилось (# New) и сколько осталось (# Delta). Если количество объектов определённого типа стабильно растёт от снимка к снимку — это признак утечки.
Shallow size, Retained size и Retainers
- Shallow size — сколько памяти занимает сам объект.
- Retained size — сколько памяти освободится, если удалить объект (объект плюс всё, что удерживается только через него).
- Retainers — дерево, показывающее, кто ссылается на объект. Проходя по этому дереву снизу вверх, находят корневую ссылку, из-за которой объект не собирается GC: забытый обработчик события, замыкание, запись в глобальном кеше.
Поиск по классам объектов
В представлении Summary объекты группируются по конструктору (Array, Closure, (string) и т.д.). Полезно искать собственные классы приложения (например, User, Request) с аномально большим числом инстансов между снимками.
Типичные причины утечек в Node.js
- Забытые слушатели событий (
EventEmitter), которые не снимаются черезremoveListener. - Замыкания, удерживающие ссылки на большие объекты — например, callback в таймере или очереди задач.
- Растущие глобальные кеши или Map без ограничения размера и TTL.
- Незакрытые соединения (сокеты, стримы), которые держат буферы в памяти дольше, чем нужно.
Дополнительные инструменты
Помимо ручного анализа в DevTools, для production используют clinic.js heapprofiler, утилиту 0x, а также автоматическое снятие снапшота при превышении порога через process.memoryUsage().rss.
Что хочет услышать интервьюер
Понимание, что heap snapshot — это снимок графа объектов кучи V8 с их размерами и ссылками
Знание, что утечку ищут не по одному снапшоту, а по сравнению нескольких (Comparison view)
Умение объяснить разницу между shallow size и retained size
Понимание концепции retainers и способность найти корневую ссылку, удерживающую объект
Знание типичных источников утечек в Node.js: EventEmitter, замыкания, кеши, соединения
Пример: Снятие heap snapshot программно
const v8 = require('v8');
const fs = require('fs');
// делаем снимок кучи и пишем его в файл .heapsnapshot
function writeHeapSnapshot(filename) {
const snapshotStream = v8.getHeapSnapshot();
const fileStream = fs.createWriteStream(filename);
snapshotStream.pipe(fileStream);
}
writeHeapSnapshot('before.heapsnapshot');
// ... нагрузка на приложение ...
writeHeapSnapshot('after.heapsnapshot');
// далее оба файла открываются во вкладке Memory в Chrome DevTools
Пример: Типичный источник утечки: забытый слушатель
class Cache {
constructor(emitter) {
this.data = new Map();
// подписка без отписки — emitter будет держать ссылку на this вечно
emitter.on('update', (item) => this.data.set(item.id, item));
}
destroy(emitter, handler) {
// правильно: снимаем слушатель при уничтожении объекта
emitter.removeListener('update', handler);
}
}
Типичные ошибки
Делают вывод об утечке по одному снапшоту, а не по сравнению нескольких во времени
Путают shallow size и retained size
Не знают про режим Comparison в Chrome DevTools и ищут утечку вручную по общему списку объектов
Забывают вызвать сборку мусора перед сравнением снапшотов, из-за чего в диффе остаётся мусор, который скоро будет собран
Не могут назвать конкретные причины утечек в Node.js (только общие слова про 'память течёт')


