Сборка мусора в JavaScript: как управляется память

Содержание

О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».

В JavaScript память освобождается автоматически — не нужно ни malloc, ни free. Но «автоматически» не значит «без ошибок»: утечки случаются. Разберём, как это работает и как их избежать.

Принцип: достижимость

Сборщик мусора удаляет объекты, до которых нельзя добраться из корней программы.

let user = { name: 'Аня' };   // объект достижим через переменную user

user = null;                  // последняя ссылка убрана
// теперь { name: 'Аня' } недостижим → будет удалён сборщиком

Корни — это глобальный объект, локальные переменные активных функций, стек вызовов. Объект достижим, если от корня к нему ведёт цепочка ссылок. Как только последняя ссылка исчезает, объект становится мусором, и движок освобождает его память. Точный момент удаления вы не контролируете — это забота движка (V8 в Node.js и Chrome).

Достижимость через цепочку

let team = {
  lead: { name: 'Аня' },      // достижим через team.lead
};

team.lead = null;             // убрали ссылку внутри team
// { name: 'Аня' } теперь недостижим → мусор

Важно: достижимость идёт по цепочке ссылок, а не по «использованию». Объект может быть больше не нужен вашему коду, но если на него есть хоть одна живая ссылка — он достижим и остаётся в памяти. Именно на этом строятся утечки.

Утечка: объект нужен сборщику, но не вам

Утечка памяти — это когда объект достижим, но уже бесполезен. Сборщик не может его удалить, потому что ссылка есть, а память растёт.

Частые источники утечек

Что течёт

Забытые ссылки

  • setInterval без clearInterval
  • Слушатель без removeEventListener
  • Растущий массив/Map кэша
  • Замыкание, держащее большой объект

Как лечить

Убрать ссылку

  • clearInterval(id)
  • removeEventListener(fn)
  • Ограничить размер кэша, чистить
  • Обнулить ссылку: ref = null

Забытый таймер

// ❌ интервал держит колбэк и всё, что он замыкает, — навсегда
setInterval(() => {
  doSomething(bigData);        // bigData не освободится, пока интервал жив
}, 1000);

// ✅ сохранить id и остановить, когда не нужно
const id = setInterval(() => doSomething(bigData), 1000);
// ...
clearInterval(id);             // теперь колбэк и bigData могут быть собраны

Не снятый слушатель

// ❌ обработчик держит ссылку на элемент и замкнутые данные
element.addEventListener('click', handler);
element.remove();              // элемент убран из DOM, но handler его держит!

// ✅ снять обработчик
element.removeEventListener('click', handler);

Это та же проблема снятия обработчиков: анонимную функцию снять нельзя, поэтому обработчики делают именованными.

Растущая коллекция

// ❌ кэш растёт без предела — классическая утечка
const cache = {};
function remember(key, value) {
  cache[key] = value;          // никогда не чистится
}

Кэши, логи, массивы истории, которые только пополняются, — частый источник. Решение: ограничивать размер, чистить старое или использовать WeakMap, где ключи не удерживают объекты от сборки.

Замыкания и память

Замыкания удерживают переменные из внешней области — это их работа, но и риск:

function createHandler() {
  const bigData = new Array(1000000).fill('x');   // большой объект
  return function () {
    console.log('клик');       // bigData не используется, но замкнут!
  };
}

Возвращённая функция замыкает всю область, включая bigData, даже если не обращается к нему, — и держит его в памяти, пока жива сама. Обычно движок оптимизирует такие случаи, но полагаться на это не стоит: не замыкайте лишнего.

Поиск утечек на практике

  • DevTools → Memory — снимки кучи (heap snapshot): делают два снимка с промежутком и смотрят, что выросло. Растущее число объектов между снимками — след утечки;
  • DevTools → Performance — график памяти: если после сборок мусора базовый уровень растёт, а не возвращается, — утечка;
  • в Node.jsprocess.memoryUsage() и флаги профилирования.

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


Смежные темы: замыкания — что удерживают; события DOM — снятие слушателей; управление памятью в Node.js. Полный список — в уроках JavaScript.

Частые вопросы

Как работает сборка мусора в JavaScript?
Движок автоматически освобождает память объектов, до которых нельзя добраться из корней программы — глобальных переменных, стека вызовов. Если на объект нет ни одной ссылки, он считается мусором и удаляется. Вручную освобождать память не нужно.
Что такое достижимость?
Объект достижим, если до него есть цепочка ссылок от корней — глобального объекта, локальных переменных активных функций. Пока объект достижим, он остаётся в памяти. Как только последняя ссылка исчезает, он становится кандидатом на удаление.
Почему в JavaScript всё равно бывают утечки памяти?
Из-за случайно сохранённых ссылок: забытый setInterval, не снятый обработчик события, растущий массив или Map, замыкание, держащее большой объект. Объект достижим через эту ссылку, поэтому сборщик его не трогает, хотя он уже не нужен.
Как найти и устранить утечку памяти?
Через вкладку Memory в DevTools: снимают снимки кучи и сравнивают рост. Устраняют, убирая лишние ссылки — clearInterval для таймеров, removeEventListener для слушателей, очистку коллекций и обнуление ненужных ссылок.
Нужно ли вручную управлять памятью в JavaScript?
Нет, память освобождается автоматически. Задача разработчика — не мешать сборщику, то есть не удерживать ненужные ссылки. Понимание достижимости помогает писать код без утечек, а не управлять памятью напрямую.