Утечки памяти и GC в Node

Перезапускать процесс — это не решение утечек. Нужно понимать, почему объекты не уходят: ссылки, замыкания, глобальные переменные, кэши без вытеснения, подписки на события.

Главные источники утечек

Источник Механика
Замыкания Скоуп функции залочен, пока жива возвращённая ф-ция
Глобальные переменные global.x = ... — никогда не GC
Подписки на события Каждый on('event') без off плодит callback
Кэши без TTL/лимита Map растёт бесконечно
Ссылки в long-lived объектах arr.push(big) и не очистил
OS-дескрипторы GC их не освобождает

«Garbage Collection, который у нас есть в ноде, объекты операционной системы он не освобождает и не отпускает. Кроме того, мы сами должны следить за их освобождением.»

Структура памяти процесса Node

const { rss, heapTotal, heapUsed, external } = process.memoryUsage;
// rss — вся память процесса (включая C++ и сторонние буферы)
// heapTotal — выделено V8 под кучу
// heapUsed — занято объектами JS
// external — память, удерживаемая JS-объектами вне V8 heap (Buffer, ArrayBuffer)

«Память делится на: код C++, код JS, стек, куча, external. Куча (heap) чистится garbage collection.»

Как искать утечки

  1. --inspect Node + Chrome DevTools → Memory tab.
  2. Three-snapshot technique: снять heap snapshot → нагрузить → снапшот → нагрузить → снапшот. Сравнить «Comparison», смотреть на объекты, число которых растёт между всеми тремя.
  3. --expose-gc + global.gc для форсированного GC перед снапшотом.
  4. Sampling heap profiler — выявить кто аллоцирует.

Кэш без вытеснения

// УТЕЧКА: Map растёт бесконечно
const cache = new Map();
function get(k) {
  if (!cache.has(k)) cache.set(k, compute(k));
  return cache.get(k);
}

// Решение: LRU или WeakMap
import { LRUCache } from 'lru-cache';
const cache = new LRUCache({ max: 500 });

Подводные камни

  • setInterval без clearInterval держит callback и весь его scope.
  • DOM-листенеры в Node-окружениях (jsdom) — то же.
  • WeakMap/WeakSet/WeakRef не держат ключ — но и не итерируются.
  • Memory growth ≠ leak. JS-приложение может расти и потом резко обвалить heap при major GC.

Источники

См. также