Skip to main content

Збирач сміття (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, а живе посилання на те, що вам уже не потрібне.

Швидкий приклад

Рушій керує пам'яттю так:

  1. Виділяє пам'ять під новий об'єкт:

    javascript
    let user = { name: 'Tim' }; // memory allocated
  2. Змінна перестає посилатися на об'єкт:

    javascript
    user = null; // the old reference is gone
  3. GC розуміє, що об'єкт { name: 'Tim' } недосяжний, і в найближчому циклі звільнить пам'ять.

Ключове поняття: досяжність (reachability)

У JS пам'ять очищається не вручну, а за принципом досяжності.

Досяжні (reachable) об'єкти:

  • глобальні змінні (window, global, globalThis);
  • локальні змінні в стеку, поки виконується функція;
  • об'єкти, на які посилаються інші досяжні об'єкти.

Недосяжні (unreachable):

  • об'єкти, на які немає посилань із досяжних місць.
javascript
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

Приклад із ланцюжком і циклом посилань

javascript
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. Якщо потім:

javascript
person = null;

обидва об'єкти стають недосяжними, бо до них більше не дійти від кореня, і GC звільнить обидва попри циклічне посилання. JS-збирач уміє розпізнавати такі цикли: він рахує не кількість посилань, а досяжність від коренів.

Алгоритм роботи на прикладі V8

V8 (рушій Chrome і Node.js) використовує алгоритм mark-and-sweep (позначити і прибрати):

  1. Mark (позначення). GC проходить від кореневих об'єктів (root: window, global, стек викликів) і позначає все, що досяжне.
  2. Sweep (очищення). Усе, що не було позначене, видаляється з пам'яті.
  3. 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 з коду застосунку, тому будь-яка оптимізація зводиться до того, щоб вчасно відпускати посилання, а не «просити» збирача попрацювати.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.