Можно ли вручную вызвать 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]]