Що таке G1 GC
G1 прийшов на зміну застарілому Parallel GC завдяки ключовій властивості: передбачуваності затримок (Predictable Latency). Розробник може задати бажану максимальну тривалість па...
🟢 Junior Level
G1 GC (Garbage-First Garbage Collector) — це серверний збирач сміття за замовчуванням у Java (починаючи з Java 9), спроєктований для багатопроцесорних систем із великими обсягами оперативної пам’яті (від 4 ГБ до десятків гігабайтів).
G1 прийшов на зміну застарілому Parallel GC завдяки ключовій властивості: передбачуваності затримок (Predictable Latency). Розробник може задати бажану максимальну тривалість паузи Stop-The-World за допомогою параметра:
-XX:MaxGCPauseMillis=200 # Цільова максимальна пауза 200 мс (за замовчуванням)
Чому він називається «Garbage-First»?
У традиційних збирачах покоління пам’яті були неперервними фізичними блоками. G1 розбиває всю купу на тисячі рівних незалежних комірок — регіонів.
У процесі роботи G1 безперервно відстежує відсоток сміття в кожному регіоні. Коли настає час збирання, він у першу чергу очищає ті регіони, де найбільше сміття («Garbage-First»), забезпечуючи максимальне вивільнення пам’яті за мінімальний час.
Побутова аналогія
- Традиційний GC: Прибиральниця намагається за один захід вимити всі підлоги в 50-поверховому офісному центрі. Будівлю перекривають на 3 години (довгий Stop-The-World).
- G1 GC: Прибиральниця знає, що в неї є суворо 15 хвилин перерви. Вона заходить лише в ті 3 кабінети, де сьогодні святкували день народження й розлили каву (Garbage-First), швидко наводить там лад і повертається до роботи, вкладаючись у регламент.
🟡 Middle Level
Регіональна структура купи в G1
Уся оперативна пам’ять купи (Java Heap) ділиться на ~2048 однакових регіонів розміром від 1 МБ до 32 МБ (розмір завжди дорівнює ступеню двійки та обчислюється автоматично під час старту JVM):
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐
│ E │ S │ O │ F │ E │ H │ H │ O │
│ (Eden)│ (Surv)│ (Old) │(Free) │ (Eden)│ (Hum) │ (Hum) │ (Old) │
└───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘
Ролі регіонів динамічні:
- E (Eden): Регіони для розміщення нових об’єктів.
- S (Survivor): Регіони для об’єктів, що пережили Minor GC.
- O (Old): Регіони для довгоживучих об’єктів.
- H (Humongous): Неперервний ланцюжок регіонів для розміщення гігантських об’єктів (розміром $> 50\%$ від розміру регіону).
- F (Free): Пул вільних регіонів.
Важливо: Регіони одного покоління не зобов’язані бути суміжними! Регіон, який сьогодні був Eden, після збирання сміття очищається і завтра може стати Old або Survivor.
Ключові структури: CSet проти RSet
Розуміння різниці між CSet та RSet — найчастіше запитання на співбесідах рівня Middle+:
┌────────────────────────────────────────────────────────┐
│ 1. Collection Set (CSet) — ЩО ЗБИРАЄМО: │
│ Набір регіонів, обраних для евакуації (очищення) │
│ в поточній паузі. При Young GC CSet складається з │
│ усіх Eden та Survivor регіонів. При Mixed GC до │
│ CSet додаються найбільш забруднені регіони Old Gen. │
├────────────────────────────────────────────────────────┤
│ 2. Remembered Set (RSet) — ЗВІДКИ ПОСИЛАЮТЬСЯ: │
│ Персональна структура ДЛЯ КОЖНОГО РЕГІОНУ. │
│ Зберігає список зовнішніх регіонів, які посилаються │
│ на об'єкти всередині поточного регіону. │
│ Завдяки RSet збирачу не потрібно сканувати всю купу,│
│ щоб зібрати один ізольований регіон! │
└────────────────────────────────────────────────────────┘
Повний цикл збирання сміття в G1
- Young GC (STW): У міру заповнення регіонів Eden їхні живі об’єкти евакуюються (копіюються) у вільні регіони Survivor або Old.
- Concurrent Marking Cycle (Фонова розмітка):
- Initial Mark (STW): Розмічаються корені GC Roots (поєднується з Young GC).
- Concurrent Mark: Фоновий обхід графа об’єктів паралельно з роботою застосунку за алгоритмом SATB (Snapshot-At-The-Beginning).
- Remark (STW): Фіналізація розмітки, спустошення буферів SATB.
- Cleanup (STW): Повністю порожні регіони повертаються в пул Free.
- Mixed GC (Змішані збирання): Очищається Young Gen плюс пачка найбільш «брудних» регіонів Old Gen. Виконується серією з декількох коротких збирань (за замовчуванням до 8 циклів).
🔴 Senior Level
Аварія евакуації: Evacuation Failure (to-space exhausted)
Найнебезпечніша ситуація в G1:
- Збирач намагається скопіювати живий об’єкт із регіону CSet, але в пулі вільних регіонів (Free regions) немає місця.
- У логах з’являється маркер:
(to-space exhausted)абоEvacuation Failure. - Що робить JVM: Збирач екстрено зупиняє евакуацію, розгортає посилання назад та ініціює Full GC. До Java 10 Full GC у G1 був однопотоковим і призводив до зупинок на 30–60 секунд. Починаючи з Java 10, Full GC став паралельним (
Parallel Full GC), але все одно залишається неприйнятним для production із жорстким SLA.
Великі об’єкти (Humongous Allocations)
Якщо розмір об’єкта перевищує 50% від розміру регіону G1, він визнається Humongous:
- Алокується безпосередньо в ланцюжок неперервних регіонів Old Generation.
- Humongous-об’єкти не евакуюються (не копіюються), що усуває накладні витрати на їхнє переміщення.
- Небезпека: Часті алокації великих масивів спричиняють фрагментацію пулу вільних регіонів. Якщо для масиву розміром 10 МБ не знайдеться 3 неперервних вільних регіони по 4 МБ, G1 негайно звалиться у Full GC.
- Рішення: Явне збільшення розміру регіону:
-XX:G1HeapRegionSize=16mабо32m.
Дедуплікація рядків: String Deduplication
Прапорець -XX:+UseStringDeduplication (доступний у G1 з Java 8u20):
- Під час фази евакуації фоновий потік G1 сканує рядкові об’єкти, що пережили кілька збирань.
- Якщо збирач знаходить два різні об’єкти
Stringз однаковим вмістом, він переприв’язує внутрішнє полеbyte[] valueу другого рядка на масив першого рядка. - Дублюючий масив байтів видаляється збирачем.
- Заощаджує від 10% до 25% купи в enterprise-застосунках без зміни бізнес-коду.
Практичний тюнінг G1 у Production
# 1. Задаємо цільову паузу (не ставити нижче 50-100 мс!)
-XX:MaxGCPauseMillis=200
# 2. Вмикаємо адаптивний розрахунок старту розмітки (увімкнений за замовчуванням з Java 9)
-XX:+G1UseAdaptiveIHOP
# 3. Збільшуємо розмір регіону при частих Humongous allocations
-XX:G1HeapRegionSize=16m
# 4. Вмикаємо дедуплікацію рядків
-XX:+UseStringDeduplication
# 5. Детальне логування GC (Java 9+)
-Xlog:gc*,gc+phases=debug:file=/var/log/app-gc.log:time,uptime,pid:filecount=5,filesize=100M
⚠️ Головне правило тюнінгу G1: НІКОЛИ не встановлюйте прапорець
-Xmn! Фіксація розміру Young Generation намертво позбавляє G1 можливості стискати та розширювати Eden для утримання часу паузи в межахMaxGCPauseMillis.
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь (Elevator Pitch)
«G1 GC — регіональний збирач сміття за замовчуванням у Java 9+, що поєднує високу пропускну здатність і передбачувані затримки. Купа розбивається на ~2048 динамічних регіонів (від 1 до 32 МБ). Збирач очищає пам’ять інкрементно, обираючи регіони з найбільшою кількістю сміття («Garbage-First»), щоб вкластися в цільову паузу
-XX:MaxGCPauseMillis(за замовчуванням 200 мс). Вхідні посилання між регіонами відстежуються структурами RSet, а цільовий набір очищення формується в CSet. Конкурентна розмітка виконується за алгоритмом SATB, а очищення Old Gen відбувається порціями під час Mixed GC».
3–4 каверзних запитання з відповідями
- Чому збирач сміття називається «Garbage-First»?
- Відповідь: Під час фонової фази розмітки G1 визначає точний обсяг живих і мертвих даних у кожному регіоні купи. На фазі Mixed GC збирач сортує регіони Old Generation за щільністю сміття і бере в обробку насамперед ті регіони, де сміття найбільше. Це гарантує максимальний обсяг звільненої пам’яті за відведений бюджет часу паузи.
- У чому різниця між Collection Set (CSet) та Remembered Set (RSet)?
- Відповідь: CSet (Collection Set) — це набір регіонів, які обрані для евакуації просто зараз у поточній паузі збирання сміття (усі живі об’єкти з них будуть переміщені, а самі регіони очищені). RSet (Remembered Set) — це допоміжна структура даних, що існує в кожного регіону і зберігає посилання на нього з усіх інших регіонів купи. RSet дозволяє очистити будь-який окремий регіон без необхідності повного сканування решти купи.
- Що відбувається в разі виникнення помилки Evacuation Failure (
to-space exhausted)?- Відповідь: Це аварійний стан, коли під час Minor або Mixed GC збирач сміття копіює вижилі об’єкти, але виявляє, що вільних регіонів у купі більше немає. G1 скасовує переміщення решти об’єктів, фіксує їх на поточних місцях і запускає глобальний Stop-The-World Full GC для всієї пам’яті. Це призводить до тривалого зависання сервісу і вказує на необхідність збільшення купи або оптимізації генерації сміття.
- Чому під час використання G1 категорично не можна фіксувати розмір Young Generation прапорцем
-Xmn?- Відповідь: Головна сила G1 — адаптивність. Щоб витримувати цільовий час паузи
MaxGCPauseMillis, збирач перед кожним циклом динамічно змінює кількість регіонів, виділених під Young Generation (від 5% до 60% купи). Якщо примусово задати-Xmn, розмір Young Gen стає жорсткою константою, евристичний алгоритм G1 вимикається, і JVM більше не може контролювати час пауз, що призводить до непередбачуваних затримок.
- Відповідь: Головна сила G1 — адаптивність. Щоб витримувати цільовий час паузи
Типові помилки та Red Flags
- 🚩 Встановлення прапорця
-Xmnразом із G1 GC (ламає весь механізм адаптивного керування паузами). - 🚩 Виставлення нереалістично маленької цільової паузи
-XX:MaxGCPauseMillis=10(G1 не встигне зібрати сміття, купа переповниться і виникне Full GC). - 🚩 Плутанина між структурами CSet (що збираємо) та RSet (хто посилається).
- 🚩 Ігнорування проблеми Humongous-об’єктів під час обробки важких вхідних масивів даних.
Пов’язані теми
- Що таке покоління в GC — покоління всередині регіонів G1
- Що таке ZGC — порівняння G1 з надшвидким ZGC
- Що таке stop-the-world — паузи в циклах збирання
- Які GC мінімізують stop-the-world паузи — порівняльний аналіз low-latency GC
- Що таке Old Generation (Tenured) — специфіка збирань Mixed GC