← назад к разделу

Сборщик мусора удаляет недостижимые объекты, а не забытые ресурсы. Если ссылка осталась в кеше или обработчике, объект жив. Если соединение или дескриптор не закрыт, память может быть ограничена уже не только V8.

heapUsed и RSS

process.memoryUsage().heapUsed показывает занятую кучу V8, а rss - всю память процесса: код, нативные буферы, стеки и другие области. Рост RSS не всегда означает утечку JavaScript-объектов.

Для первой диагностики снимают метрики до и после одинаковой серии запросов и дают сборке завершиться. Для снимков кучи используют inspector или сигнал --heapsnapshot-signal и сравнивают типы объектов и их retainers.

import { getHeapStatistics } from 'node:v8';

const before = getHeapStatistics();
console.log({
  used: before.used_heap_size,
  total: before.heap_size_limit,
});

Точное сравнение делают под повторяемой нагрузкой. Один рост после прогрева кеша еще не доказывает утечку.

Четыре источника утечек

  • Map или массив без политики вытеснения, куда пишут каждый новый ключ.
  • Слушатели EventEmitter, добавленные на запрос и не снятые после завершения.
  • Замыкания, которые захватывают большой запрос ради одного поля.
  • Таймеры, сокеты, workers и файловые дескрипторы без явного завершения.

MaxListenersExceededWarning - полезный сигнал, а не украшение логов. Лимит слушателей не повышают, пока не доказано, что рост является нормальным устройством приложения.

Как подтвердить исправление

Сначала воспроизводят рост на небольшом сценарии, затем убирают одну причину и повторяют тот же сценарий. Успех - возврат памяти и числа слушателей к стабильному уровню после серии циклов, а не меньший пик на одном графике.

Для соединений и workers проверяют не только heap, но и число открытых handles и внешние ресурсы. unref меняет удержание event loop, но не закрывает сам ресурс и не отменяет операцию.

Коротко

  • heapUsed и rss - разные метрики памяти.
  • Утечка JavaScript требует достижимой ссылки, утечка ресурса - незавершенной операции.
  • Кеши, listeners, замыкания и незакрытые handles проверяют в первую очередь.
  • Снимки кучи и retainers помогают найти владельца объектов.
  • unref не заменяет close, clearTimeout или terminate.

Что почитать дальше