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