Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке збирач сміття (Garbage Collector)? Розкажіть детально, як він влаштований». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Збирач сміття (GC)** - частина рантайму (V8 у Chrome/Node, SpiderMonkey у Firefox тощо), яка автоматично звільняє пам'ять, зайняту об'єктами, до яких більше не можна дістатися з коренів (roots) програми. **Ключове:** головний критерій - досяжність (reachability): якщо об'єкт недосяжний з коренів, його пам'ять може бути звільнена.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Що таке збирач сміття (GC) **Збирач сміття** - це частина рантайму (V8 у Chrome/Node, SpiderMonkey у Firefox тощо), яка **автоматично звільняє пам'ять**, зайняту об'єктами, до яких більше **не можна дістатися з коренів (roots)** програми. Головний критерій: **досяжність (reachability)**. Якщо об'єкт досяжний з коренів (глобальні об'єкти, стек поточних викликів, регістри, активні замикання, DOM-дерево тощо), він живий. Якщо **не досяжний**, його пам'ять може бути звільнена. --- ## Базовий алгоритм: mark-and-sweep 1. **Mark (позначення):** обходимо граф об'єктів від коренів, позначаємо всі досяжні. 2. **Sweep (змітання):** проходимо по купі і **звільняємо** всі **непозначені**. Додатково може виконуватися **compaction (ущільнення)** - переміщення об'єктів, що вціліли, для усунення фрагментації пам'яті. --- ## Поколіннєвий GC (generational) Спостереження: "більшість об'єктів живуть недовго". Звідси: - **Young/нове покоління (nursery/new space):** сюди потрапляють нові об'єкти; часті швидкі збирання (**minor GC / scavenging**). - **Old/старе покоління (tenured/old space):** сюди **просувають (promote)** ті об'єкти, що пережили декілька мінорних збирань; збираються рідше, але дорожче (**major GC**). - Технічно реалізується через: - **bump-pointer allocation** (дуже швидке виділення поспіль), - **copying GC** для young (два напівпростори: from-space -> to-space), - **mark-(sweep|compact)** для old. Щоб молоде і старе покоління коректно посилалися одне на одного, рантайм використовує: - **write barrier** + **card marking / remembered set** - недороге записування метаданих при створенні посилань "з old у young", щоб мінорний GC знав, які старі об'єкти можуть тримати молоді. --- ## Інкрементальність, конкурентність і три-кольорова позначка Повна "зупинка світу" (stop-the-world) на великі паузи неприйнятна в UI/сервері, тому застосовують: - **Tri-color marking (білий/сірий/чорний):** формальна модель позначення, що дозволяє **інкрементально** просувати обхід графа. - **Incremental GC:** позначення розбивають на маленькі порції, між ними застосунок продовжує працювати. - **Concurrent GC:** частина роботи (наприклад, розмітка) виконується **паралельно** з застосунком в іншому потоці. - **Incremental/parallel marking & sweeping**, **compaction** з мінімізацією пауз. Ідея: зменшувати jank/фризи за рахунок маленьких передбачуваних пауз. --- ## Чому не "reference counting" Лічильники посилань ламаються на **циклах** (A->B->A), що призводить до витоків. Тому в JS - **трасувальні GC** (mark-and-sweep/compact), де цикл не проблема: якщо компонент недосяжний цілком, він збереться. --- ## Weak-посилання і фіналізація - **WeakMap/WeakSet:** ключі (для WeakMap) і елементи (у WeakSet) **не перешкоджають збиранню**. Щойно об'єкт більше ніде не досяжний, GC **може** його звільнити, запис зникне "сам". Це корисно для кешів/мемоізації без витоків. - **FinalizationRegistry:** дозволяє виконати колбек *після* того, як об'єкт зібрано. **Важливі попередження:** час виклику **не детермінований**, порядок не гарантований, виклику може не бути (при завершенні процесу). Не можна покладатися на це для логіки; тільки для "м'яких" побічних дій (очищення зовнішніх кешів тощо). Приклад безпечного кеша: ```javascript const cache = new WeakMap(); // ключ - об'єкт, значення - обчислення function heavy(obj) { if (cache.has(obj)) return cache.get(obj); const val = compute(obj); cache.set(obj, val); return val; } ``` --- ## Що конкретно в V8 (Chrome/Node) - у двох словах - Купа розділена на простори: **new space** (напівпростори копіювального GC), **old space** (mark-sweep/compact), **code space**, **map space**, **large object space** (без переміщення). - **Minor GC (Scavenger):** швидко копіює живі об'єкти з from-space в to-space; ті, що пережили N разів - **promotion** в old. - **Major GC:** точна (precise) розмітка, потім sweep/compact; інкрементальна і конкурентна, щоб скорочувати паузи. - Тонкощі: write barriers, remembered sets, parallel marking/sweeping, евристики за тиском пам'яті і фрагментацією. --- ## Коли GC запускається - Немає вільного місця для нового виділення (allocation failure). - Тиск пам'яті від ОС/хоста. - Евристики за ростом купи і частотою мінорних збирань. - У DevTools можна тригернути вручну (тільки для відладки). --- ## Практика: як не "підставляти" GC Типові джерела витоків у JS-застосунках: 1. **Довгоживучі посилання**: кеші, синглтони, замикання, глобальні структури, збережені DOM-вузли. 2. **Події/таймери**: не зняли обробник або `setInterval`; висячі `Promise`-посилання. 3. **DOM<->JS цикли**: тримаєте посилання на видалений з DOM вузол або його замикання. 4. **Необмежені структури**: Map/Array ростуть без очищення. Що робити: - Відключати **обробники подій** і **таймери**: ```javascript const handler = () => {}; el.addEventListener('click', handler); // ... el.removeEventListener('click', handler); ``` - Для кешів, прив'язаних до об'єктів - **WeakMap/WeakSet**. - Уникати непотрібних глобальних/модульних посилань. - Обережно з замиканнями: не замикайте зайві великі об'єкти. - Обмежувати розмір кешів (LRU) і очищати їх. - У React - стежити за **cleanup** у `useEffect`. --- ## Інструменти діагностики - **Chrome DevTools -> Performance/Memory**: Heap snapshot, Allocation instrumentation -> шукаємо "Detached HTML nodes", ріст retainers. - `performance.memory` (браузер, експериментально) - груба телеметрія. - **Node.js**: `--inspect`, heap snapshots, `clinic/heapdump`, профілювальники, прапори `--max-old-space-size`. - **Ідея тесту:** прожени сценарій N разів -> пам'ять повинна стабілізуватися. Постійний ріст - сигнал витоку. --- ## Важливі властивості й обмеження - **GC не детермінований**: ти не контролюєш точний час збирання. - **Finalizers** не гарантуються до моменту виходу процесу/вкладки. - **Великі паузи** все ще можливі, але сучасні GC прагнуть робити їх короткими. --- ### Коротке резюме GC видаляє **недосяжні** об'єкти. Сучасні реалізації використовують **поколіннєвий, інкрементальний, конкурентний** mark-and-(sweep/compact), зменшуючи паузи. Для розробника ключове - **керувати посиланнями** (слухачі, таймери, кеші, замикання, DOM) і діагностувати поведінку пам'яті інструментами профілювання.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.