Питання 14 · Розділ 3

Що таке ZGC?

4. Моніторте Allocation Stall у логах 5. CPU overhead 5-15% — закладайте при плануванні

Мовні версії: English Russian Ukrainian

🟢 Junior Level

ZGC (Z Garbage Collector) — збирач з мінімальними паузами (< 1 мс), що не залежать від розміру Heap. Throughput на 5-15% нижчий, ніж у G1.

Головна перевага: Паузи практично не залежать від розміру Heap і зазвичай < 1 мс. При екстремальному навантаженні можливі відхилення.

Коли використовувати:

  • Real-time системи
  • High-frequency trading
  • Коли SLA < 10 мс

🟡 Middle Level

Ключові технології

1. Colored Pointers:

64-бітний вказівник:
  Біти 0-41: адреса об'єкта
  Біти 42-45: метадані (стан GC)

→ GC розуміє статус об'єкта, просто прочитавши вказівник
→ Без звернення до пам'яті об'єкта!

Colored Pointers — у 64-бітних вказівниках ZGC використовує верхні
біти для зберігання метаданих (покоління, finalizable, weak і т.д.).
JVM «розфарбовує» вказівники, економлячи окрему структуру даних.

2. Load Barriers:

При кожному читанні посилання:
  → Перевірити колір вказівника
  → Якщо об'єкт переїхав → виправити адресу (Self-healing)
  → overhead ~5-15% CPU

Load Barriers — перевірки при КОЖНОМУ читанні посилання. Якщо вказівник
«неправильний» (об'єкт переміщений), Load Barrier автоматично
виправляє вказівник (Self-Healing).

3. Concurrent Evacuation:

Об'єкти переміщуються БЕЗ зупинки додатку!
  → На відміну від G1 (Evacuation = STW)
  → Пауза тільки для сканування GC Roots

ZGC vs G1

Критерий G1 ZGC
Пауза 20-200 мс < 1 мс
Throughput Високий -5-10%
Max Heap 64 ГБ 16 ТБ
Barriers Write Load

Generational ZGC (Java 21+)

До Java 21: одне покоління
  → Сканувався весь Heap при кожному зборі

До Java 21 ZGC був без поколінь — це викликало Allocation Stalls
при високих allocation rate. Generational ZGC (Java 21+) вирішує
цю проблему.

Java 21+: Young та Old покоління
  → -50% CPU overhead
  → Allocation Stalls майже зникли

Коли НЕ використовувати

❌ CPU обмежений (90%+) → Load Barriers доб'ють
❌ Heap < 4 ГБ → G1 ефективніше
❌ 32-бітні системи → ZGC потребує 64-біт

Тюнінг

-XX:+UseZGC -XX:+ZGenerational  # Java 21+
-XX:SoftMaxHeapSize=24g          # Агресивне повернення пам'яті
-XX:ConcGCThreads=4              # Потоки GC

🔴 Senior Level

Multi-mapping

Одна фізична ділянка пам'яті маппиться на 3 віртуальні адреси
  → Різні комбінації біт метаданих
  → ОС не бачить конфлікту вказівників
  → Дозволяє працювати з "кольоровими" вказівниками

Multi-mapping — техніка відображення однієї області Heap на кілька
віртуальних адрес, щоб кодувати метадані в адресах вказівників.

Self-Healing Pointers

Load Barrier при читанні:
  1. Прочитати вказівник
  2. Перевірити біт Remapped
  3. Якщо не Remapped → об'єкт переїхав
  4. Обчислити нову адресу
  5. Оновити вказівник у Heap (CAS)
  6. Повернути нову адресу

→ Вказівник "лікується" при кожному читанні

Allocation Stall Deep Dive

Причина: GC не встигає розмічати сміття
  → Потоки додатку чекають звільнення пам'яті

Рішення:
  1. Збільшити Heap
  2. Збільшити ConcGCThreads
     `ConcGCThreads` за замовчуванням = кількість ядер / 4.
     Збільште, якщо бачите Allocation Stall; зменште, якщо GC забирає занадто багато CPU.
  3. Generational ZGC (Java 21+)
  4. Зменшити Allocation Rate

Best Practices

  1. Java 21+ — використовуйте Generational ZGC
  2. ConcGCThreads = кількість ядер / 4
  3. SoftMaxHeapSize для контейнерів
  4. Моніторте Allocation Stall у логах
  5. CPU overhead 5-15% — закладайте при плануванні

Резюме для Senior

  • ZGC = Colored Pointers + Load Barriers + Conc Evacuation
  • Паузи < 1 мс константні, не залежать від Heap
  • Generational (Java 21+) = -50% overhead
  • Self-healing вказівники через Load Barriers
  • Allocation Stall = головний ризик
  • CPU overhead 5-15% — ціна за low latency

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • ZGC: паузи < 1 мс, не залежать від розміру Heap (до 16 ТБ); throughput на 5-15% нижчий за G1
  • Colored Pointers: верхні біти 64-бітного вказівника зберігають метадані (покоління, стан GC)
  • Load Barriers: при кожному читанні посилання перевірка кольору вказівника + Self-healing (якщо об’єкт переїхав)
  • Concurrent Evacuation: об’єкти переміщуються БЕЗ STW (на відміну від G1, де Evacuation = STW)
  • Multi-mapping: одна фізична ділянка Heap маппиться на 3 віртуальні адреси для кодування метаданих
  • Generational ZGC (Java 21+): поділ на Young/Old → -50% CPU overhead, Allocation Stalls майже зникли
  • Коли НЕ використовувати: CPU bound (90%+), Heap < 4 ГБ, 32-бітні системи

Часті уточнюючі запитання:

  • Чому ZGC повільніший за G1 по throughput? — Load Barriers при кожному читанні посилання додають 5-15% CPU overhead
  • Що таке Allocation Stall у ZGC? — GC не встигає розмічати сміття → потоки додатку чекають; рішення: збільшити Heap, ConcGCThreads, або Generational ZGC
  • Що таке Self-Healing вказівники? — Load Barrier читає вказівник → перевіряє біт Remapped → якщо об’єкт переїхав, обчислює нову адресу і оновлює вказівник (CAS)
  • Навіщо Multi-mapping? — Одна область Heap на 3 віртуальні адреси → кодування метаданих в адресах без конфлікту вказівників

Червоні прапорці (НЕ говорити):

  • «ZGC використовує Write Barriers» — ZGC використовує Load Barriers (при читанні); G1 використовує Write Barriers (при записі)
  • «ZGC працює на 32-бітних системах» — ZGC потребує 64-бітну систему (Colored Pointers використовують верхні біти)
  • «ZGC завжди краще G1» — для CPU-bound або batch processing G1 дає кращий throughput

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

  • [[12. Які алгоритми GC існують]]
  • [[13. Що таке G1 GC]]
  • [[15. Що таке Shenandoah GC]]
  • [[16. Що таке stop-the-world]]
  • [[17. Які GC мінімізують stop-the-world паузи]]