Чи можна вручну викликати GC?
Виняток: ExplicitGCInvokedConcurrent для планового Concurrent GC (вимагає глибокого розуміння вашого GC).
🟢 Junior Level
Так, можна: System.gc() — запит до JVM на запуск збирання сміття.
System.gc(); // "Будь ласка, прибери сміття"
АЛЕ це антипатерн!
- GC сам знає, коли прибирати
- Ручний виклик = зупинка додатку
- У production НЕ використовуйте System.gc() для регулярного очищення. Виняток: ExplicitGCInvokedConcurrent для планового Concurrent GC (вимагає глибокого розуміння вашого GC).
Коли можна:
- У тестах
- У бенчмарках
- Перед зняттям дампа
🟡 Middle Level
Чому це погано
1. Full GC = Stop-The-World
→ Всі потоки зупиняються
→ Пауза: секунди на великих Heap
2. GC краще знає, коли збирати
→ Само-навчальні алгоритми (G1, ZGC)
→ Ручний виклик скидає статистику
3. Неефективно
→ В Eden ще багато місця
→ Марна трата CPU
Direct Buffers — виняток
// NIO виділяє пам'ять поза Heap
ByteBuffer buf = ByteBuffer.allocateDirect(100_000_000);
// 100 МБ поза -Xmx!
// JVM не бачить навантаження на Native Memory
// → GC не запускається (Heap порожній)
// → Native Memory закінчується
// → NIO викликає System.gc()!
// Якщо вимкнути: -XX:+DisableExplicitGC
// → OOM у Native Memory при порожньому Heap!
Прапорці управління
# Повністю ігнорувати System.gc()
-XX:+DisableExplicitGC
# Виконувати конкурентно (G1)
-XX:+ExplicitGCInvokedConcurrent
Альтернатива: jcmd
# Зовнішній виклик GC (без зміни коду)
jcmd <PID> GC.run
🔴 Senior Level
ExplicitGCInvokedConcurrent
Замість Full GC STW:
→ G1 запускає фоновий цикл
→ Додаток працює
→ Мінімальні паузи
«Золота середина» для production
GC Thrashing маскування
// ❌ Спроба «полікувати» витік
try {
allocate();
} catch (OutOfMemoryError e) {
System.gc(); // Маскує проблему!
allocate(); // Все одно впаде
}
// Додаток входить у Thrashing:
// 95% часу на GC, 5% на роботу
JIT деоптимізація
Full GC може призвести до class unloading, що викликає деоптимізацію
гарячих методів. Code Cache очищується окремо (CodeCache sweeper),
але class unloading при Full GC впливає на JIT-скомпільовані методи.
→ Після GC: інтерпретація
→ JIT перекомпілює заново
→ Throughput падає
Діагностика
# Хто викликає System.gc()?
# 1. GC Logs
-Xlog:gc*
# → Шукайте: "Pause Full (System.gc())"
# 2. JFR
# → Подія "System GC"
# → Повний стек-трейс виклику
# 3. jcmd
jcmd <PID> GC.run # Ручний виклик для тестів
Best Practices
- НЕ використовуйте System.gc() у бізнес-коді
- ExplicitGCInvokedConcurrent для production
- jcmd GC.run для діагностики
- DisableExplicitGC обережно (Direct Buffers!)
- Шукайте того, хто викликає через JFR
- Бенчмарки — єдине виправдання
Резюме для Senior
- System.gc() = антипатерн у production
- Direct Buffers = єдиний виняток
- ExplicitGCInvokedConcurrent = компроміс
- JFR = знайти того, хто викликає
- JIT деоптимізація = прихований overhead
- jcmd = зовнішній виклик для тестів
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
System.gc()— запит JVM на Full GC (STW, секунди на великих Heap); антипатерн у production- GC краще знає, коли збирати: само-навчальні алгоритми (G1, ZGC) адаптуються до Allocation Rate; ручний виклик скидає статистику
- Direct Buffers — виняток: NIO пам’ять поза Heap; GC не бачить навантаження (Heap порожній) → Native Memory закінчується → NIO викликає
System.gc() -XX:+DisableExplicitGC— повністю ігноруєSystem.gc(); небезпечно для Direct Buffers (OOM у Native Memory при порожньому Heap)-XX:+ExplicitGCInvokedConcurrent— «золота середина»: G1 запускає фоновий конкурентний цикл замість Full GC STW- JIT деоптимізація: Full GC → class unloading → деоптимізація гарячих методів → інтерпретація → throughput падає
- Діагностика:
-Xlog:gc*(шукайте “Pause Full (System.gc())”), JFR (подія “System GC” зі стек-трейсом)
Часті уточнюючі запитання:
- Чому Direct Buffers — виняток? —
ByteBuffer.allocateDirect()виділяє native memory поза Heap; GC не бачить навантаження на Heap → не запускається → Native Memory закінчується → NIO викликаєSystem.gc() - Що таке GC Thrashing? — Додаток входить у цикл: 95% часу на GC, 5% на роботу;
System.gc()маскує проблему, не вирішує - Чому JIT деоптимізація — проблема? — Full GC → class unloading → гарячі методи перекомпілюються заново → throughput падає на секунди
- Коли легітимно викликати System.gc()? — Бенчмарки (JMH), тести, перед зняттям дампа для очищення сміття
Червоні прапорці (НЕ говорити):
- «Викликаю System.gc() після великих операцій для оптимізації» — GC сам знає; ручний виклик = STW + скидання статистики
- «DisableExplicitGC завжди безпечний» — Direct Buffers: OOM у Native Memory при порожньому Heap
- «System.gc() лікує витік пам’яті» — маскує проблему; додаток все одно увійде у GC Thrashing
Пов’язані теми:
- [[28. Чому не варто викликати System.gc()]]
- [[4. Що таке Garbage Collection]]
- [[19. Що станеться при OutOfMemoryError]]
- [[13. Що таке G1 GC]]
- [[16. Що таке stop-the-world]]