Вопрос 27 · Раздел 3

Можно ли вручную вызвать GC?

Исключение: ExplicitGCInvokedConcurrent для планового Concurrent GC (требует глубокого понимания вашего GC).

Версии по языкам: English Russian Ukrainian

🟢 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

  1. НЕ используйте System.gc() в бизнес-коде
  2. ExplicitGCInvokedConcurrent для production
  3. jcmd GC.run для диагностики
  4. DisableExplicitGC осторожно (Direct Buffers!)
  5. Ищите вызывающего через JFR
  6. Бенчмарки — единственное оправдание

Резюме для 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]]