Коротка відповідь
juniorЗбирач сміття у JS‑рушіях — це автоматичний процес, що видаляє об’єкти, до яких більше немає живих посилань. Він працює за принципом «підрахунку посилань» (reference counting) з додатковим збірником «мріт» (mark‑and‑sweep), що проходить по графу об’єктів, позначає живі вузли і очищує непомічені. Під час збірки застосовуються оптимізації, такі як «incremental GC» і «generational GC», щоб мінімізувати паузи й розподіл пам’яті.
Повне пояснення
Що це і навіщо
- Збирач сміття (garbage collector, GC) автоматично звільняє пам’ять, яку вже не використовують програми. Це критично для JS‑рушіїв (V8, SpiderMonkey, JavaScriptCore), де розробник не керує пам’яттю вручну.
Ключові принципи
- Reference counting: кожен об’єкт зберігає лічильник посилань. Коли лічильник стає нульовим, об’єкт вважається недоступним.
- Mark‑and‑sweep: збірник проходить по графу об’єктів, починаючи з коренів (global object, call stack), позначає живі вузли («mark»). Після цього усі непомічені об’єкти видаляються («sweep»).
- Generational GC: розділення пам’яті на молоду (new) і стару (old) генерації. Більшість об’єктів короткоживі, тому збірка молодої генерації швидка і частіша.
- Incremental / Concurrent GC: збірник розбиває роботу на маленькі кроки, щоб уникнути довгих пауз.
Як це працює (логіка)
- Підготовка: рушій визначає корені – глобальні об’єкти, локальні змінні на стеку, замикання.
- Mark: рекурсивний DFS по графу об’єктів, позначає живі вузли.
- Sweep: проходить по пам’яті, видаляє непомічені об’єкти.
- Compact (не всіх рушій): переміщує живі об’єкти, щоб звільнити фрагментовану пам’ять.
Практика і реалізація
- У V8:
--max-old-space-sizeвстановлює розмір старої генерації.--gc-intervalконтролює частоту збірки. - У Node.js:
global.gc()(з--expose-gc) викликає ручну збірку.
Тестування
// Jest + jsdom: перевірка, що об’єкт видаляється після знищення посилань
let obj = { a: 1 };
const weakRef = new WeakRef(obj);
obj = null; // позначаємо, що більше немає сильних посилань
setTimeout(() => {
expect(weakRef.deref()).toBeUndefined();
}, 1000);
Дизайн‑рішення
- WeakMap / WeakSet: колекції, що не збираються як посилання.
- FinalizationRegistry: виконує колбек після збирання об’єкта.
Безпека і приватність
- GC не впливає на безпечність даних; однак неправильне використання
WeakRefможе призвести до утримання об’єктів.
Особливості в контексті JS‑рушій
- V8: generational + incremental,
--turboоптимізує збірку. - SpiderMonkey: «nursery» + «tenured» області,
--gc-interval. - JavaScriptCore: «mark‑compact» з підтримкою
--gc-interval.
Оптимізація, типові помилки
- Memory leaks: циклічні посилання без
WeakMap. - Over‑allocation: надмірна кількість об’єктів у молодій генерації.
- Frequent GC: занадто часті виклики
global.gc(). - Large objects: великі масиви/об’єкти, що не помічаються швидко.
- Stack overflow: рекурсивні об’єкти без базового випадку.
- WeakRef misuse: очікування, що
deref()поверне об’єкт після збірки. - FinalizationRegistry race: колбек може виконатися в будь-який момент.
- Unbounded closures: замикання, що утримують великі об’єкти.
Cheatsheet
- GC trigger:
global.gc()(Node),--expose-gc. - Memory limits:
--max-old-space-size=4096(4 GB). - Weak collections:
new WeakMap(),new WeakSet(). - Finalization:
new FinalizationRegistry((heldValue) => { … }).
Follow‑up питання
- Як працює generational GC у V8?
- Що таке
WeakRefі коли його використовувати? - Які ризики пов’язані з
FinalizationRegistry?