Сборка мусора в 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.js —
process.memoryUsage()и флаги профилирования.
Практический вывод простой: память освобождается сама, ваша задача — не удерживать ненужное. Останавливайте таймеры, снимайте слушатели, чистите коллекции, не замыкайте лишнего. Понимание достижимости важнее любых ручных приёмов.
Смежные темы: замыкания — что удерживают; события DOM — снятие слушателей; управление памятью в Node.js. Полный список — в уроках JavaScript.