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

Как работает Garbage Collection в JS?

2.0 Middle🔥 112 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Управление памятью в JavaScript

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

1. Основной принцип: Reachability

// Объект доступен (reachable), если его можно получить из корня
let user = {
  name: "Alice",
  email: "alice@example.com"
};

// user доступен из глобального контекста
// Его память не будет освобождена

// Если переменная больше не содержит ссылку на объект:
user = null;  // Теперь объект недоступен
// GC удалит объект из памяти

// Статистика памяти
console.log(performance.memory);
// heapSizeLimit: максимальный размер heap
// totalJSHeapSize: текущий размер heap
// usedJSHeapSize: используемая память

2. Алгоритм Mark and Sweep

// Это основной алгоритм GC в современных браузерах

// Шаг 1: Mark (пометка)
// GC проходит по всем объектам и помечает доступные

// Шаг 2: Sweep (удаление)
// GC удаляет объекты, которые не помечены

// Пример:
let a = { value: 1 };
let b = { value: 2 };
let c = a;  // c указывает на тот же объект что и a

a = null;   // a больше не использует объект
b = null;   // b больше не использует свой объект

// Объект { value: 1 } всё ещё доступен через c
// Объект { value: 2 } недоступен - будет удалён GC

c = null;   // Теперь оба объекта недоступны
// GC удалит оба объекта

3. References и циклические ссылки

// Старые браузеры с Reference Counting имели проблемы с циклами

// ПРОБЛЕМА в старых браузерах:
let parent = {};
let child = {};

parent.child = child;
child.parent = parent;  // Циклическая ссылка

parent = null;
child = null;

// В алгоритме Mark and Sweep это не проблема
// Но в Reference Counting объекты не удаляются (утечка памяти)

// Mark and Sweep удаляет оба объекта, так как они недоступны

4. Утечки памяти (Memory Leaks)

// УТЕЧКА: Ненужные глобальные переменные
window.largeArray = new Array(1000000).fill('data');
// Массив никогда не удалится, занимает память

// УТЕЧКА: Забытые listener
const button = document.querySelector('button');
button.addEventListener('click', () => {
  console.log('Clicked');
});
// Listener остаётся даже если элемент удалён из DOM

// ИСПРАВЛЕНИЕ: Удаляй listener
button.removeEventListener('click', handler);

// УТЕЧКА: Бесконечные таймеры
setInterval(() => {
  console.log('Every second');
}, 1000);
// Никогда не останавливается

// ИСПРАВЛЕНИЕ: Сохраняй ID и очищай
const timerId = setInterval(() => { }, 1000);
clearInterval(timerId);

5. Утечки в React

// УТЕЧКА: useEffect без очистки
export function UserCard({ userId }) {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Update');
    }, 1000);
    
    // Таймер никогда не очистится!
    // При unmount компонента утечка
  }, [userId]);

  return <div>User</div>;
}

// ИСПРАВЛЕНИЕ: Возврати cleanup функцию
export function UserCard({ userId }) {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Update');
    }, 1000);
    
    return () => clearInterval(timer);  // Очистка
  }, [userId]);

  return <div>User</div>;
}

// УТЕЧКА: Listener на события
export function Observer({ onData }) {
  useEffect(() => {
    window.addEventListener('message', onData);
    // Listener не удаляется
  }, [onData]);

  return <div>Observer</div>;
}

// ИСПРАВЛЕНИЕ
export function Observer({ onData }) {
  useEffect(() => {
    window.addEventListener('message', onData);
    return () => window.removeEventListener('message', onData);
  }, [onData]);

  return <div>Observer</div>;
}

6. WeakMap и WeakSet

// WeakMap хранит слабые ссылки на объекты
// Объекты могут быть удалены GC даже если они в WeakMap

const cache = new WeakMap();

let user = { id: 1, name: 'Alice' };
cache.set(user, { cached: true });

console.log(cache.get(user));  // { cached: true }

user = null;  // Объект может быть удалён GC
// Запись в WeakMap также удалится

// Обычный Map хранит сильные ссылки
const strongCache = new Map();
let obj = { id: 1 };
strongCache.set(obj, 'data');

obj = null;  // Объект НЕ удалится, остаётся в Map

7. Профилирование памяти

// Chrome DevTools: Memory tab

// Snapshot - снимок памяти
// Allocation timeline - как растёт память
// Allocation profiler - какие объекты занимают память

// Вручную вызови GC (если DevTools открыт в специальном режиме)
if (window.gc) {
  gc();  // Принудительная сборка мусора
}

// Проверь память
console.log(performance.memory.usedJSHeapSize);  // Текущее использование

// Поищи утечки
const arr = [];
for (let i = 0; i < 1000000; i++) {
  arr.push({ data: new Array(100).fill('x') });
}
// Как растёт usedJSHeapSize при добавлении?

8. Оптимизация для GC

// ПЛОХО: Создание много объектов
function render() {
  for (let i = 0; i < 1000; i++) {
    const point = { x: Math.random(), y: Math.random() };
    processPoint(point);
  }
}

// ХОРОШО: Переиспользуй объекты
const point = { x: 0, y: 0 };
function render() {
  for (let i = 0; i < 1000; i++) {
    point.x = Math.random();
    point.y = Math.random();
    processPoint(point);
  }
}

// ПЛОХО: Создание больших объектов в цикле
for (let i = 0; i < 100; i++) {
  const data = new Array(10000).fill(0);
}

// ХОРОШО: Переиспользуй буфер
const buffer = new Array(10000);
for (let i = 0; i < 100; i++) {
  buffer.fill(0);
}

9. Поколенческий GC

// Современные браузеры используют поколенческий GC
// Young generation - новые объекты (часто собираются)
// Old generation - старые объекты (редко собираются)

// Новый объект
let young = { id: 1 };

// После нескольких GC циклов переходит в Old generation
young.id = 2;  // Переходит в Old
young.id = 3;
young.id = 4;

// Объекты в Old собираются реже, т.к. обычно долгоживущие
// Это оптимизирует производительность

Ответ: GC использует алгоритм Mark and Sweep для удаления недоступных объектов. Профилируй утечки в DevTools, очищай listener в useEffect, и избегай глобальных переменных.