← Назад к вопросам

Как работает garbage collector в JavaScript?

2.0 Middle🔥 141 комментариев
#Node.js и JavaScript#Кэширование и производительность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Garbage Collector в JavaScript и Node.js

Garbage Collector (GC) — это критический механизм управления памятью в JavaScript, который автоматически освобождает память от объектов, которые больше не используются. Вот углубленное объяснение, как это работает в Node.js.

Основной Принцип: Mark-and-Sweep

Современный V8 engine (используется в Node.js) использует алгоритм Mark-and-Sweep:

Фаза 1 — Mark (Маркировка)

  • GC начинает с так называемых "root" объектов (глобальные переменные, стек вызовов)
  • Рекурсивно проходит по всем объектам, до которых можно дотянуться из root'а
  • Помечает эти объекты как "живые"

Фаза 2 — Sweep (Очистка)

  • Все непомеченные объекты считаются "мёртвыми"
  • Память, занимаемая мёртвыми объектами, освобождается
  • Живые объекты остаются в памяти

Пример:

// Объект будет помечен как живой
const user = { name: 'John', age: 30 };

// Теперь нет ссылки на объект — можно удалить
const anotherRef = user; // Ссылка есть
const stillAlive = null; // Ссылка удалена, но user всё ещё живой через anotherRef

// Когда обе ссылки удалены:
const thirdRef = null; // Теперь объект мёртв и может быть удалён GC

Generational Hypothesis и Young/Old Generation

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

Young Generation (Молодое поколение)

  • Небольшая область памяти (~1-8 MB)
  • Большинство объектов здесь рождаются и умирают
  • GC здесь срабатывает часто, но быстро (Scavenge algorithm)
  • Время: миллисекунды

Пример:

for (let i = 0; i < 1000000; i++) {
  // Каждый объект рождается, используется и умирает в Young Generation
  const tempObj = { id: i, data: Math.random() };
  // После цикла tempObj удаляется
}
// Young GC срабатывает несколько раз, но это быстро

Old Generation (Старое поколение)

  • Большая область памяти
  • Объекты, пережившие несколько Young GC циклов, переходят сюда
  • GC здесь срабатывает реже, но может быть долгим
  • Время: десятки-сотни миллисекунд (full GC может быть вредна для production)

Пример:

// Этот объект будет жить долго
const appState = {
  users: [],
  config: { /* ... */ },
  // Множество других полей
};
// После нескольких Young GC циклов перейдёт в Old Generation

Поиск Утечек Памяти

Признаки утечки памяти:

  • Memory usage растёт и не падает со временем
  • GC не может освободить память
  • Node.js процесс в итоге падает с OOM (Out of Memory)

Пример утечки:

const cache = {}; // Глобальный кеш

function processUser(userId, userData) {
  // ❌ Плохо: cache растёт бесконечно
  cache[userId] = userData; // Никогда не удаляется!
}

// Вызываем миллион раз...
for (let i = 0; i < 1000000; i++) {
  processUser(i, { /* large data */ });
}
// Память исчерпана!

Правильный вариант с LRU кешем:

const LRU = require('lru-cache');

const cache = new LRU({
  max: 1000, // максимум 1000 записей
  ttl: 1000 * 60 * 5, // время жизни 5 минут
});

function processUser(userId, userData) {
  cache.set(userId, userData); // Автоматически удаляет старые
}

Инструменты для Анализа GC

1. Node.js Flags

# Логирование GC событий
node --trace-gc app.js

# Более подробный GC лог
node --trace-gc --trace-gc-verbose app.js

# Установка максимального heap'а
node --max-old-space-size=4096 app.js

2. Heap Snapshots в Chrome DevTools

// Инспектируем heap snapshot
// 1. Запускаем: node --inspect app.js
// 2. Открываем chrome://inspect
// 3. Смотрим heap snapshots

3. Профилирование в коде

const v8 = require('v8');
const fs = require('fs');

// Выполняем код
complexOperation();

// Снимаем heap snapshot
const heapSnapshot = v8.writeHeapSnapshot();
console.log(`Heap snapshot written to ${heapSnapshot}`);

Практические Советы для Backend Разработчика

1. Избегайте Глобальных Переменных

// ❌ Плохо
let requestCount = 0;
app.get('/api/users', (req, res) => {
  requestCount++; // Растёт бесконечно!
});

// ✅ Хорошо
const metrics = new Map(); // Используем structured data
app.get('/api/users', (req, res) => {
  // Metrics имеет lifecycle
});

2. Очищайте Event Listeners

// ❌ Плохо — утечка памяти
const eventEmitter = new EventEmitter();
eventEmitter.on('event', () => { /* ... */ });
// Listener никогда не удаляется

// ✅ Хорошо
const listener = () => { /* ... */ };
eventEmitter.on('event', listener);
// ... позже
eventEmitter.off('event', listener);

3. Используйте Streams для Больших Данных

// ❌ Плохо — вся файл в памяти
const data = fs.readFileSync('huge-file.json');
const json = JSON.parse(data);

// ✅ Хорошо — потоком
fs.createReadStream('huge-file.json')
  .pipe(JSONStream.parse('*'))
  .on('data', (chunk) => {
    processChunk(chunk); // обрабатываем по частям
  });

4. Избегайте Circular References

// ❌ Плохо — циклические ссылки
const parent = { name: 'parent' };
const child = { name: 'child', parent };
parent.child = child;
// GC может их не удалить, если нет других ссылок

// ✅ Хорошо
const parent = { name: 'parent' };
const child = { name: 'child' };
parent.child = child;
// child.parent = null; // Удаляем обратную ссылку явно

GC в Production

Мониторинг:

  • Отслеживайте heap usage через metrics (Prometheus, NewRelic)
  • Устанавливайте алерты, если GC pause > 100ms
  • Full GC должен быть редким (не каждую секунду)

Optimizing:

  • Если часто случаются full GC, это признак утечки
  • Увеличивайте heap size осторожно (не бесконечно)
  • Чаще переинициализируйте контейнеры (docker) с ограничением памяти

Заключение

Garbage Collector в Node.js — это автоматический процесс, но как разработчик ты должен:

  • Понимать, как он работает
  • Писать код, который помогает GC (избегая утечек)
  • Мониторить heap usage в production
  • Оптимизировать, когда видишь проблемы

Хороший backend разработчик пишет код не только функционально верно, но и эффективно по памяти.