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

Які алгоритми GC існують?

4. Не використовуйте G1, якщо: 5. Не починайте з тюнінгу — спочатку перевірте витоки 6. Моніторте GC логи 7. Allocation Stall = збільште Heap або ConcGCThreads

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

🟢 Junior Level

Алгоритми GC — різні способи “прибирання сміття” в пам’яті.

Три основні підходи:

Алгоритм Як працює Аналогія
Copying Копіює живі в нову область Переїзд у новий дім
Mark-Sweep Позначає живих, видаляє мертвих Викинути сміття з кімнати
Mark-Compact Позначає + зміщує живих Прибирання + розстановка по місцях

Чому різні алгоритми:

  • Copying ефективний, коли сміття багато (копіюємо тільки 2% живих).
  • Mark-Sweep ефективний, коли сміття мало (не потрібно копіювати).
  • Mark-Compact потрібен для боротьби з фрагментацією.

Основні GC в Java:

  • G1 GC — за замовчуванням (Java 9+)
  • ZGC (Java 15+) — паузи < 1 мс, не залежать від розміру Heap. Throughput на 5-10% нижчий за G1.
  • Parallel GC — для максимальної швидкості обробки

🟡 Middle Level

GC Triangle

         Latency (затримка)
          / \
         /   \
  Throughput ── Footprint

Не можна оптимізувати всі три!

Latency — наскільки довго додаток «завмирає» при GC (паузи).
Throughput — який процент часу додаток працює, а не збирає сміття.
Footprint — скільки пам'яті споживає JVM.

Сучасні GC

GC Пауза Throughput Для чого
Serial 100-500 мс Низький Маленькі додатки
Parallel 100-1000 мс Максимальний Big Data, batch
G1 20-200 мс Хороший Web, Spring Boot
ZGC < 1 мс -5-10% Real-time, HFT
Shenandoah < 1 мс -5-10% Альтернатива ZGC

Allocation Stall

Додаток створює сміття швидше, ніж GC збирає
  → Потік зупиняється і чекає GC
  → Пауза: сотні мілісекунд

Рішення: більше Heap або менше алокацій

Full GC

Усі GC мають "план Б" — Full GC
  → Якщо конкурентна збірка не справляється
  → Повний STW всіх поколінь
  → Ознака витоку або неправильного налаштування

🔴 Senior Level

Write vs Load Barriers

G1: Write Barriers
  → При записі посилання: позначити Card Table
  → overhead при записі

ZGC: Load Barriers
  → При читанні посилання: перевірити колір вказівника
  → Self-healing: виправити адресу на льоту
  → overhead при читанні

SATB vs Incremental Update

SATB (Snapshot-At-The-Beginning):
  → Позначає все, що було живе на початок розмітки
  → Floating Garbage (об'єкти, що стали недосяжними після snapshot,
    будуть зібрані в наступному циклі)
  → Використовується в G1

Incremental Update:
  → Відстежує нові посилання
  → Менше Floating Garbage
  → Більше overhead

Generational ZGC (Java 21+)

Поділ на Young/Old покоління
  → -50% CPU overhead
  → Young GC частіше і швидше
  → Практично немає Allocation Stalls

Epsilon GC

«No-op GC» — тільки алокація
  → Взагалі не збирає сміття
  → При OOM → JVM падає

Epsilon GC — «порожній» збирач: взагалі не збирає сміття.
Викидає OOM як тільки Heap заповниться.
Корисний для бенчмарків та тестів з гарантованим часом життя.

Навіщо:
  - Бенчмарки (виключити вплив GC)
  - Короткоживучі функції
  - Off-heap робота

Best Practices

  1. G1 GC — за замовчуванням для більшості
  2. ZGC (Java 21+) — для SLA < 10 мс
  3. Parallel — для Big Data
  4. Не використовуйте G1, якщо:
    • SLA < 10 мс — беріть ZGC
    • Batch processing без latency-вимог — беріть Parallel GC
  5. Не починайте з тюнінгу — спочатку перевірте витоки
  6. Моніторте GC логи
  7. Allocation Stall = збільште Heap або ConcGCThreads

Резюме для Senior

  • GC Triangle: Latency ↔ Throughput ↔ Footprint
  • Barriers: Write (G1) vs Load (ZGC)
  • SATB = Floating Garbage trade-off
  • Generational ZGC — найкращий low-latency GC
  • Allocation Stall = катастрофа для latency
  • Epsilon — для тестів, не production
  • Найкраще сміття = не створене

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

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

  • GC Triangle: не можна оптимізувати Latency, Throughput і Footprint одночасно
  • Copying — копіює живі (Young Gen, ефективний при 98% сміття); Mark-Sweep — позначає + видаляє (Old Gen); Mark-Compact — позначає + зміщує (без фрагментації)
  • Serial GC: 1 потік, довгі паузи — НЕ для серверів; Parallel GC: макс throughput, довгі паузи — для Big Data
  • G1 GC: регіони 1-32 МБ, Mixed GC, паузи 20-200 мс — баланс; ZGC: Colored Pointers + Load Barriers, < 1 мс, overhead 5-15%
  • Allocation Stall: додаток створює сміття швидше, ніж concurrent GC → потік завмирає
  • SATB (G1): Floating Garbage (об’єкти, що стали недосяжними після snapshot, будуть зібрані в наступному циклі)
  • Epsilon GC: «no-op», не збирає сміття — тільки для бенчмарків

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

  • Чому ZGC використовує Load Barriers, а G1 — Write Barriers? — ZGC перевіряє вказівник при читанні (self-healing); G1 позначає Card Table при записі. Load Barriers дорожчі (~5-15 ns vs ~1-2 ns)
  • Що таке Floating Garbage? — Об’єкти, померлі ПІСЛЯ початку розмітки; не будуть зібрані в цьому циклі (SATB trade-off)
  • Коли Full GC — ознака проблеми? — Коли трапляється регулярно (кожні хвилини); ознака витоку або неправильного налаштування
  • Чому Generational ZGC (Java 21+) кращий за single-generational? — Поділ на Young/Old → -50% CPU overhead, Young GC частіше і швидше

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

  • «ZGC завжди краще G1» — ZGC жертвує throughput на 5-15%; для batch processing G1/Parallel краще
  • «Я почну з тюнінгу GC параметрів» — спочатку перевірте витоки та код; тюнінг GC не виправить bad code
  • «Serial GC підходить для production сервера» — 1 потік, довгі паузи; тільки для маленьких CLI-утиліт

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

  • [[4. Що таке Garbage Collection]]
  • [[13. Що таке G1 GC]]
  • [[14. Що таке ZGC]]
  • [[15. Що таке Shenandoah GC]]
  • [[17. Які GC мінімізують stop-the-world паузи]]