Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает Garbage Collector (сборщик мусора)
Garbage Collector (GC) — это механизм в JavaScript движке, который автоматически освобождает память от объектов, которые больше не используются. Это ключевой механизм управления памятью в JavaScript.
Основная идея
В любой момент времени объект может быть в двух состояниях:
- Достижимый (reachable) — приложение может до него добраться
- Недостижимый (unreachable) — на него нет ссылок, приложение не может его использовать
GC удаляет недостижимые объекты.
Как определить достижимость?
Root объекты
Это объекты, которые всегда считаются достижимыми:
- Глобальные переменные (window, global)
- Переменные в стеке вызовов
- Замыкания функций
let globalVar = { name: 'Global' }; // Достижимо из window
function foo() {
let localVar = { name: 'Local' }; // Достижимо из стека
return localVar;
}
let obj = foo(); // Всё ещё достижимо, т.к. присвоено переменной
// После выхода из foo() localVar удаляется из стека, но obj всё ещё достижим
Граф достижимости
GC строит граф объектов и их ссылок:
let person = {
name: 'Alice',
address: {
city: 'New York'
},
hobbies: ['reading', 'coding']
};
// Граф:
// person (достижимо из переменной)
// ├── address (достижимо через person.address)
// │ └── city: 'New York'
// └── hobbies (достижимо через person.hobbies)
// ├── 'reading'
// └── 'coding'
person = null; // person становится недостижимым
// GC удалит person, address, hobbies и их содержимое
Алгоритмы GC
1. Mark-and-Sweep (метка и очистка)
Это наиболее распространённый алгоритм. Работает в 3 этапа:
Этап 1: Mark (маркировка)
GC проходит по графу и помечает все достижимые объекты:
let obj1 = { value: 1 };
let obj2 = { value: 2 };
let obj3 = { value: 3 };
obj1.ref = obj2; // obj1 -> obj2
// Mark-фаза:
// Стартуем с root (глобальные переменные)
// Помечаем obj1 (достижимо)
// Помечаем obj2 (достижимо через obj1.ref)
// obj3 НЕ помечен (недостижим)
Этап 2: Sweep (очистка)
GC проходит по памяти и удаляет неотмеченные объекты:
// После Sweep-фазы:
// obj1 сохранён
// obj2 сохранён
// obj3 удалён, память освобождена
Пример Mark-and-Sweep
// Состояние памяти
let cache = {};
cache.image1 = new Image(); // большой объект, помечен
cache.image2 = new Image(); // большой объект, помечен
cache.image3 = new Image(); // большой объект, помечен
// Удаляем ссылку
delete cache.image3;
// Mark phase: image3 не помечен
// Sweep phase: image3 удалён, память освобождена
2. Generational GC (поколенческая сборка мусора)
Основана на предположении: молодые объекты часто умирают, старые живут долго.
Объекты делятся на поколения:
// Поколение 0 (молодое): быстро удаляется
for (let i = 0; i < 1000; i++) {
const temp = { /* большие данные */ };
processData(temp);
// temp удаляется в конце итерации, GC его быстро подхватит
}
// Поколение 1+ (старое): собирается реже
const cache = new Map();
// cache существует весь жизненный цикл приложения
// GC редко проверяет его
3. Incremental GC (инкрементальная сборка)
Вместо одной большой паузы, GC работает порциями:
// Без incremental GC (вариант, когда GC работает слишком долго):
// Время: [--- главный код ---][---- GC пауза 50ms ----][--- главный код ---]
// ^ пользователь видит заморозку
// С incremental GC:
// Время: [код][GC 5ms][код][GC 5ms][код][GC 5ms][код]
// Плавнее, нет заметных пауз
Проблемы с памятью и утечки
Memory leak (утечка памяти)
Это когда объект становится недостижимым, но на него всё ещё есть ссылка:
Пример 1: Event listener
// ПЛОХО: утечка памяти
class Component {
constructor() {
document.addEventListener('click', () => {
this.handleClick();
});
// Слушатель создан, но никогда не удалён
// Он держит ссылку на this
}
handleClick() {
console.log('Clicked');
}
}
// Компонент удалён, но слушатель остался
let comp = new Component();
comp = null; // Компонент НЕ удалится, т.к. слушатель ещё на него ссылается
// ХОРОШО: управляем слушателем
class Component {
constructor() {
this.handler = () => this.handleClick();
document.addEventListener('click', this.handler);
}
destroy() {
document.removeEventListener('click', this.handler);
}
}
let comp = new Component();
comp.destroy(); // Слушатель удалён
comp = null; // Теперь компонент удалится
Пример 2: Замыкание
// ПЛОХО: утечка памяти
function createCache() {
const largeData = new Array(10000000); // большой массив
return function() {
console.log(largeData.length); // замыкание держит ссылку на largeData
};
}
const getLength = createCache();
// largeData не удалится, пока существует getLength
getLength(); // 10000000
// ХОРОШО: освобождаем замыкание
const getLength = createCache();
getLength();
getLength = null; // Теперь largeData может быть собран
Пример 3: Timer
// ПЛОХО: утечка памяти
function startTimer() {
const bigData = new Array(1000000);
setInterval(() => {
console.log(bigData.length); // timer держит ссылку на bigData
}, 1000);
// Timer не очищен, bigData не удалится
}
// ХОРОШО: управляем timer
function startTimer() {
const bigData = new Array(1000000);
const timerId = setInterval(() => {
console.log(bigData.length);
}, 1000);
return () => clearInterval(timerId); // функция очистки
}
const stop = startTimer();
stop(); // Очищаем timer
Пример 4: React компонент
// ПЛОХО: утечка памяти в React
function UserProfile() {
useEffect(() => {
const fetchUser = async () => {
const user = await fetch(`/api/user`);
setUser(user);
};
fetchUser();
// Нет cleanup функции!
}, []);
return <div>{user?.name}</div>;
}
// Если компонент размонтируется до завершения fetch, setUser вызовется на удалённом компоненте
// ХОРОШО: cleanup функция
function UserProfile() {
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
const user = await fetch(`/api/user`);
if (isMounted) setUser(user); // Проверяем, что компонент ещё mounted
};
fetchUser();
return () => {
isMounted = false; // cleanup: отменяем обновления
};
}, []);
return <div>{user?.name}</div>;
}
Инструменты для отладки памяти
Chrome DevTools Memory
- Откройте DevTools -> Memory tab
- Возьмите snapshot памяти
- Проведите операции в приложении
- Возьмите ещё один snapshot
- Сравните — какие объекты не удалились
Performance Monitoring
// Проверь, как меняется используемая память
if (performance.memory) {
console.log(`Используется: ${performance.memory.usedJSHeapSize / 1048576} MB`);
console.log(`Лимит: ${performance.memory.jsHeapSizeLimit / 1048576} MB`);
}
Best Practices
- Удаляй слушатели событий
element.addEventListener('click', handler);
// ...
element.removeEventListener('click', handler);
- Очищай timers
const id = setTimeout(() => { /* ... */ }, 1000);
clearTimeout(id);
- Используй cleanup в useEffect
useEffect(() => {
// ...
return () => { /* cleanup */ };
}, []);
- Обнуляй большие объекты
let bigArray = new Array(1000000);
// когда больше не нужен
bigArray = null; // помогает GC
- Избегай циклических ссылок (В современных движках это не проблема, но старайся)
const obj = {};
obj.self = obj; // циклическая ссылка
obj = null; // GC может это обработать
Итог
Garbage Collector — это невидимый помощник, который:
- Отслеживает достижимость объектов
- Удаляет недостижимые объекты
- Освобождает память автоматически
Ваша задача — помочь ему, избегая утечек памяти:
- Правильно управляйте слушателями
- Очищайте timers
- Используйте cleanup функции в React
- Помните о замыканиях