> Как выявлять и исправлять утечки памяти (JavaScript)

Уровень: senior · Роль: frontend · Язык: JavaScript · Категория: Технические вопросы

Компании: ЭНИРАН

Стек: Node.js, JavaScript

> Пример ответа

Короткий ответ

Утечки памяти в Node.js выявляются через heap snapshots, профилирование с Chrome DevTools или встроенный process.memoryUsage(). Основные причины: глобальные переменные, забытые таймеры, closures, неочищенные event listeners. Исправление - удаление ссылок, очистка таймеров, использование WeakMap/WeakSet, правильное управление подписками. Для мониторинга в production применяется heapdump и автоматические дампы при превышении порога памяти.

Подробное объяснение

Утечки памяти в Node.js возникают, когда объекты остаются в heap, хотя уже не нужны. Основные источники:

  • Глобальные переменные: объекты, присвоенные global или globalThis, живут до завершения процесса
  • Забытые таймеры и интервалы: setInterval без clearInterval удерживает callback и замыкание
  • Event listeners: подписки на события без removeListener или off, особенно на глобальные эмиттеры
  • Closures: функции, захватывающие большие объекты из внешнего scope, которые не освобождаются
  • Кэши без ограничений: объекты, накапливающиеся в Map или массиве без контроля размера
  • Сторонние библиотеки: некоторые модули (например, старые версии mongoose) создают скрытые ссылки

Механизм выявления: V8 предоставляет heap snapshot через v8.getHeapSnapshot(). Сравнение двух снимков до и после операции показывает, какие объекты не были собраны GC. Для production используется модуль heapdump для создания дампов по сигналу.

На практике

  1. Мониторинг: установите лимит heap через --max-old-space-size и логируйте process.memoryUsage().heapUsed каждые N секунд
  2. Профилирование: запустите приложение с флагом --inspect, откройте chrome://inspect, сделайте heap snapshot до и после повторяющейся операции
  3. Инструменты: clinic.js (Doctor, Bubbleprof, Flame) для автоматического анализа, 0x для flamegraphs
  4. Автоматизация: используйте why-is-node-running для поиска активных handles (таймеры, сокеты)
  5. Production: настройте автоматический дамп heap при превышении порога памяти (например, через process.on('exit') или мониторинг воркеров PM2)

Пример кода

JAVASCRIPT
// Плохо: утечка через глобальный кэш
const cache = {};
function processRequest(data) {
cache[data.id] = { data, timestamp: Date.now() };
// cache никогда не очищается
}
// Хорошо: ограничение размера кэша
const LRU = require('lru-cache');
const cache = new LRU({ max: 100, ttl: 1000 * 60 * 5 });
// Плохо: забытый интервал
function startPolling() {
setInterval(() => {
fetch('/api/data').then(/* ... */);
}, 1000);
// нет clearInterval при остановке
}
// Хорошо: возвращаем cleanup
function startPolling() {
const intervalId = setInterval(() => {
fetch('/api/data').then(/* ... */);
}, 1000);
return () => clearInterval(intervalId);
}
// Плохо: утечка через event listener
class Service {
constructor() {
process.on('message', this.handleMessage);
}
// нет removeListener при уничтожении
}
// Хорошо: WeakRef или явный cleanup
class Service {
constructor() {
this.boundHandler = this.handleMessage.bind(this);
process.on('message', this.boundHandler);
}
destroy() {
process.off('message', this.boundHandler);
}
}

Как отвечать на собеседовании

Начните с перечисления основных причин утечек в Node.js. Покажите понимание работы GC (mark-and-sweep, поколения). Упомяните, что утечки часто связаны с асинхронными операциями и event loop. Приведите конкретный пример из практики: как вы находили утечку через heap snapshot и что было причиной. Подчеркните важность превентивных мер: лимиты кэшей, очистка таймеров, использование WeakMap для хранения ссылок без предотвращения GC. Для senior-уровня стоит упомянуть FinalizationRegistry и WeakRef как продвинутые инструменты.

Что проверяет интервьюер

  • Понимание работы V8 memory management и GC
  • Умение диагностировать утечки без готовых инструментов (логика поиска)
  • Знание специфики Node.js: event loop, async hooks, worker threads
  • Опыт с production-мониторингом и автоматизацией
  • Способность проектировать архитектуру, устойчивую к утечкам (cleanup patterns, DI с управлением жизненным циклом)

Типичные ошибки

  • Путать утечку с обычным ростом памяти (heap растёт до порога GC - это нормально)
  • Использовать delete для Map вместо Map.prototype.delete()
  • Забывать, что closures могут удерживать целые цепочки объектов
  • Полагаться только на --expose-gc и ручной вызов global.gc() в production
  • Не учитывать, что сторонние библиотеки (например, ORM) могут создавать скрытые ссылки через internal caches
  • Игнорировать утечки в worker threads - каждый worker имеет свой heap

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью