Збирач сміття (GC) у JavaScript
Збирач сміття (Garbage Collector) це механізм JavaScript-рушія (наприклад V8), який автоматично звільняє пам'ять, зайняту об'єктами, що стали недосяжними. Тобто GC прибирає з пам'яті все, на що більше немає посилань, щоб не марнувати ресурси і не допустити переповнення пам'яті.
Теорія
TL;DR
- GC звільняє пам'ять автоматично, ручного
free()у JavaScript немає. - Критерій один: досяжність (reachability), а не «логічна потрібність» об'єкта.
- Корені досяжності: глобальний об'єкт, стек викликів, живі замикання.
- Базовий алгоритм: mark-and-sweep, іноді з фазою compact (ущільнення пам'яті).
- V8 має два режими: Minor GC (Scavenge) для молодих об'єктів і Major GC (Mark-Sweep-Compact) для старих.
- Циклічні посилання не заважають GC: важливо лише, чи можна дійти до об'єкта від кореня.
- Витік це не поломка GC, а живе посилання на те, що вам уже не потрібне.
Швидкий приклад
Рушій керує пам'яттю так:
-
Виділяє пам'ять під новий об'єкт:
javascriptlet user = { name: 'Tim' }; // memory allocated -
Змінна перестає посилатися на об'єкт:
javascriptuser = null; // the old reference is gone -
GC розуміє, що об'єкт
{ name: 'Tim' }недосяжний, і в найближчому циклі звільнить пам'ять.
Ключове поняття: досяжність (reachability)
У JS пам'ять очищається не вручну, а за принципом досяжності.
Досяжні (reachable) об'єкти:
- глобальні змінні (
window,global,globalThis); - локальні змінні в стеку, поки виконується функція;
- об'єкти, на які посилаються інші досяжні об'єкти.
Недосяжні (unreachable):
- об'єкти, на які немає посилань із досяжних місць.
let user = { name: 'Alice' };
let admin = user; // two references to one object
user = null; // still reachable through admin
admin = null; // now unreachable, GC will remove itПриклад із ланцюжком і циклом посилань
function createUser() {
const user = { name: 'Bob' };
const address = { city: 'Paris' };
user.addr = address;
address.owner = user; // circular reference
return user;
}
let person = createUser();Тут user посилається на address, а address назад на user. Якщо потім:
person = null;обидва об'єкти стають недосяжними, бо до них більше не дійти від кореня, і GC звільнить обидва попри циклічне посилання. JS-збирач уміє розпізнавати такі цикли: він рахує не кількість посилань, а досяжність від коренів.
Алгоритм роботи на прикладі V8
V8 (рушій Chrome і Node.js) використовує алгоритм mark-and-sweep (позначити і прибрати):
- Mark (позначення). GC проходить від кореневих об'єктів (root:
window,global, стек викликів) і позначає все, що досяжне. - Sweep (очищення). Усе, що не було позначене, видаляється з пам'яті.
- Compact (ущільнення). Іноді пам'ять ущільнюється, щоб прибрати фрагментацію і звільнити суцільні блоки.
Крім того, у V8 є два типи циклів:
- Minor GC (Scavenge) для «молодих» об'єктів: нових, які зазвичай живуть недовго. Працює часто і швидко.
- Major GC (Mark-Sweep-Compact) для «старих» об'єктів: тих, що пережили кілька Minor GC. Працює рідше і довше.
Як GC впливає на продуктивність
- GC працює автоматично і переважно асинхронно, але іноді спричиняє невеликі паузи (GC pause).
- Чим більше «живих» об'єктів, тим довше триває цикл збирання.
- Витоки пам'яті заважають GC: він вважає «висячі» посилання живими і не може нічого звільнити.
Оптимальний код це менше довгоживучих об'єктів плюс своєчасне розривання посилань.
Чого GC не робить
| Не робить | Чому |
|---|---|
| Не очищає все одразу | Щоб не блокувати роботу програми |
| Не бачить «логічної непотрібності» | Він дивиться лише на відсутність посилань, це не інтелект |
| Не керується вручну | Немає free() як у C++; оператор delete у JS видаляє властивість, а не пам'ять |
| Не чистить глобальні об'єкти | Вони завжди досяжні через window або globalThis |
Як дивитися на GC і пам'ять
У браузері:
- Chrome DevTools, вкладка Memory, «Heap snapshot»;
- вкладка Performance, секція Memory, кнопка «Record»;
- консоль:
performance.memory.usedJSHeapSize.
У Node.js:
- прапорець
--inspect, пакетиheapdump,clinic.js, методprocess.memoryUsage().
Підсумкова таблиця:
| Що | Опис |
|---|---|
| GC (Garbage Collector) | Механізм автоматичного звільнення пам'яті |
| Принцип | Видаляє об'єкти, до яких не можна «дійти» від кореня |
| Головний алгоритм | Mark-and-sweep |
| Проблема витоків | Об'єкт усе ще досяжний, але вже не потрібен |
| Рішення | Розривати посилання, чистити таймери і слухачів |
| Плюси | Простота, безпека роботи з пам'яттю |
| Мінуси | Потенційні паузи і приховані витоки |
Типові помилки
| Помилка | Що відбувається |
|---|---|
| Глобальні змінні | Лишаються досяжними назавжди, GC їх ніколи не прибере |
| Таймери і слухачі без очищення | Посилання утримують замикання разом з усім їхнім вмістом |
| DOM-посилання після видалення елемента | Вузол видалено з документа, але ref тримає його, тож GC безсилий |
| Замикання з великими об'єктами | Витоки через область видимості: одна маленька функція тримає мегабайти |
Окремо варто пам'ятати ще дві речі. По-перше, «об'єкт більше не потрібен» і «об'єкт недосяжний» це різні твердження: GC розуміє тільки друге. По-друге, немає способу примусово і надійно викликати GC з коду застосунку, тому будь-яка оптимізація зводиться до того, щоб вчасно відпускати посилання, а не «просити» збирача попрацювати.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.