🧠 Розділ 3 · Питання #13

Що таке 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

  1. Young GC (STW): У міру заповнення регіонів Eden їхні живі об’єкти евакуюються (копіюються) у вільні регіони Survivor або Old.
  2. Concurrent Marking Cycle (Фонова розмітка):
    • Initial Mark (STW): Розмічаються корені GC Roots (поєднується з Young GC).
    • Concurrent Mark: Фоновий обхід графа об’єктів паралельно з роботою застосунку за алгоритмом SATB (Snapshot-At-The-Beginning).
    • Remark (STW): Фіналізація розмітки, спустошення буферів SATB.
    • Cleanup (STW): Повністю порожні регіони повертаються в пул Free.
  3. 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 каверзних запитання з відповідями

  1. Чому збирач сміття називається «Garbage-First»?
    • Відповідь: Під час фонової фази розмітки G1 визначає точний обсяг живих і мертвих даних у кожному регіоні купи. На фазі Mixed GC збирач сортує регіони Old Generation за щільністю сміття і бере в обробку насамперед ті регіони, де сміття найбільше. Це гарантує максимальний обсяг звільненої пам’яті за відведений бюджет часу паузи.
  2. У чому різниця між Collection Set (CSet) та Remembered Set (RSet)?
    • Відповідь: CSet (Collection Set) — це набір регіонів, які обрані для евакуації просто зараз у поточній паузі збирання сміття (усі живі об’єкти з них будуть переміщені, а самі регіони очищені). RSet (Remembered Set) — це допоміжна структура даних, що існує в кожного регіону і зберігає посилання на нього з усіх інших регіонів купи. RSet дозволяє очистити будь-який окремий регіон без необхідності повного сканування решти купи.
  3. Що відбувається в разі виникнення помилки Evacuation Failure (to-space exhausted)?
    • Відповідь: Це аварійний стан, коли під час Minor або Mixed GC збирач сміття копіює вижилі об’єкти, але виявляє, що вільних регіонів у купі більше немає. G1 скасовує переміщення решти об’єктів, фіксує їх на поточних місцях і запускає глобальний Stop-The-World Full GC для всієї пам’яті. Це призводить до тривалого зависання сервісу і вказує на необхідність збільшення купи або оптимізації генерації сміття.
  4. Чому під час використання G1 категорично не можна фіксувати розмір Young Generation прапорцем -Xmn?
    • Відповідь: Головна сила G1 — адаптивність. Щоб витримувати цільовий час паузи MaxGCPauseMillis, збирач перед кожним циклом динамічно змінює кількість регіонів, виділених під Young Generation (від 5% до 60% купи). Якщо примусово задати -Xmn, розмір Young Gen стає жорсткою константою, евристичний алгоритм G1 вимикається, і JVM більше не може контролювати час пауз, що призводить до непередбачуваних затримок.

Типові помилки та Red Flags

  • 🚩 Встановлення прапорця -Xmn разом із G1 GC (ламає весь механізм адаптивного керування паузами).
  • 🚩 Виставлення нереалістично маленької цільової паузи -XX:MaxGCPauseMillis=10 (G1 не встигне зібрати сміття, купа переповниться і виникне Full GC).
  • 🚩 Плутанина між структурами CSet (що збираємо) та RSet (хто посилається).
  • 🚩 Ігнорування проблеми Humongous-об’єктів під час обробки важких вхідних масивів даних.

Пов’язані теми