> Как сборщик мусора находит неиспользуемые объекты в JavaScript (JavaScript)

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

Компании: ПочтаТех

Стек: JavaScript

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

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

Сборщик мусора в JavaScript находит неиспользуемые объекты через механизм достижимости (reachability). Объект считается живым, если он достижим из корневых ссылок - глобального объекта, текущего стека вызовов, локальных переменных и параметров функций. Основной алгоритм - mark-and-sweep: движок периодически обходит граф объектов от корней, помечает все достижимые, затем удаляет непомеченные. Современные движки (V8, SpiderMonkey) используют поколенческую сборку и инкрементальные циклы для минимизации пауз.

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

Механизм поиска мусора в JavaScript основан на концепции достижимости, а не на подсчёте ссылок (как в ранних версиях или в некоторых других языках). Это принципиально важно: два объекта могут ссылаться друг на друга, но если ни один из них не достижим из корня, оба считаются мусором.

Корни (roots) включают:

  • глобальный объект (window/globalThis)
  • текущие вызовы функций (стек)
  • локальные переменные и параметры в активных функциях
  • ссылки из замыканий, которые ещё могут быть вызваны
  • DOM-элементы, подключённые к документу (для браузеров)

Алгоритм mark-and-sweep работает в два этапа:

  1. Mark: движок начинает с корней и рекурсивно обходит все ссылки, помечая каждый достижимый объект.
  2. Sweep: после обхода все непомеченные объекты считаются мусором и освобождаются.

Современные движки улучшают этот базовый алгоритм:

  • Поколенческая сборка (generational GC): объекты делятся на молодое и старое поколения. Молодые объекты проверяются часто (большинство объектов умирают быстро), старые - реже.
  • Инкрементальная сборка: цикл разбивается на мелкие части, выполняемые между задачами, чтобы не блокировать основной поток.
  • Concurrent/parallel marking: разметка выполняется в фоновых потоках (в V8 - с помощью вспомогательных потоков).
  • Write barriers: при изменении ссылок движок отслеживает изменения, чтобы корректно обрабатывать ссылки из старого поколения в молодое.

Важно понимать: сборщик мусора не может определить, "нужен" ли объект логически - только достижим ли он. Если вы храните ссылку на объект в глобальной переменной, но больше не используете его, GC не освободит память. Поэтому для освобождения памяти нужно обнулять ссылки или использовать WeakMap/WeakSet.

На практике

Для фронтенд-разработчика понимание работы GC важно в следующих сценариях:

  • Утечки памяти: если вы добавляете обработчики событий и не удаляете их, или храните ссылки на DOM-элементы в глобальных структурах, GC не сможет освободить память.
  • Работа с большими данными: при обработке больших массивов или строк важно давать GC возможность освободить память после использования.
  • Использование WeakMap/WeakSet: эти структуры хранят слабые ссылки - если на ключ нет других ссылок, запись автоматически удаляется сборщиком.
  • Замыкания: замыкания удерживают ссылки на внешние переменные - если замыкание живёт долго, оно удерживает и все захваченные объекты.

Пример кода

JAVASCRIPT
// Утечка памяти: глобальная ссылка
let globalCache = [];
function processData(data) {
globalCache.push(data); // объект остаётся достижимым вечно
}
// Правильно: очищаем ссылку после использования
function processDataSafe(data) {
const result = heavyOperation(data);
// ... используем result
// после выхода из функции result станет недостижимым
}
// WeakMap для кэширования без утечек
const cache = new WeakMap();
function getCached(obj) {
if (!cache.has(obj)) {
cache.set(obj, expensiveComputation(obj));
}
return cache.get(obj);
}
// Когда obj станет недостижимым, запись в WeakMap будет удалена GC
// Удаление обработчиков событий
function setupHandler() {
const element = document.getElementById('btn');
const handler = () => { /* ... */ };
element.addEventListener('click', handler);
// Возвращаем функцию очистки
return () => element.removeEventListener('click', handler);
}

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

Начните с ключевой идеи: GC работает на основе достижимости, а не подсчёта ссылок. Затем опишите mark-and-sweep как базовый алгоритм. Упомяните, что современные движки используют поколенческую и инкрементальную сборку для производительности. Подчеркните практические следствия: GC не освобождает объекты, на которые есть ссылки, даже если они не используются логически. Приведите пример утечки через глобальные переменные или обработчики событий. Если спросят про WeakMap - объясните, как слабые ссылки помогают GC.

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

  • Понимание разницы между достижимостью и "нужностью" объекта.
  • Знание базового алгоритма mark-and-sweep.
  • Осознание ограничений GC: он не решает проблему логических утечек.
  • Понимание практических паттернов: WeakMap, удаление обработчиков, обнуление ссылок.
  • Знание современных оптимизаций (поколения, инкрементальность) - для senior-уровня это ожидаемо.

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

  • Утверждение, что GC использует подсчёт ссылок (это было в ранних версиях, но давно заменено).
  • Мнение, что GC "знает", какие объекты нужны программе - он знает только о достижимости.
  • Игнорирование проблемы циклических ссылок: циклы не мешают mark-and-sweep, но мешали бы подсчёту ссылок.
  • Утверждение, что можно "вызвать GC" из JavaScript - это невозможно, движок сам решает когда собирать.
  • Непонимание, что локальные переменные в завершённых функциях автоматически становятся недостижимыми (если не захвачены замыканием).

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

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