System.gc: примеры (JAVA)
System.gc: voidОписание System.gc
Метод System.gc() является частью стандартной библиотеки Java. Он служит подсказкой для виртуальной машины (JVM) о желательности запуска сборщика мусора (Garbage Collector). Вызов не гарантирует немедленного исполнения – решение о фактическом запуске принимает JVM в зависимости от текущего алгоритма GC, загрузки кучи и других факторов.
Метод не принимает аргументов. Его возвращаемый тип – void.
Применяется редко, в основном в тестовых сценариях или перед замером производительности для минимизации влияния несобранных объектов. В production-коде частое использование может ухудшить throughput из-за накладных расходов на вызов GC.
Эквивалентная функциональность доступна через Runtime.getRuntime().gc(), который работает идентично. Оба метода просто вызывают внутренний метод runFinalization() не вызывают – финализация происходит до или во время сбора мусора, но не гарантируется.
Примеры использования
Пример 1. Простой вызов
public class GcExample {
public static void main(String[] args) {
System.gc();
System.out.println("Метод gc вызван");
}
}Метод gc вызван
Результат выполнения на экране не отражает работу GC. Вывод может быть любым, так как GC может запуститься в фоне.
Пример 2. Вызов перед проверкой памяти
public class MemoryCheck {
public static void main(String[] args) {
long before = Runtime.getRuntime().freeMemory();
System.gc();
long after = Runtime.getRuntime().freeMemory();
System.out.println("Свободно до: " + before + ", после: " + after);
}
}Свободно до: 262144000, после: 271319040
Значения зависят от состояния кучи и работы GC. Повторные запуски могут давать разные цифры.
Пример 3. Использование с флагом -XX:+DisableExplicitGC
// Запуск: java -XX:+DisableExplicitGC GcExample
public class GcExample {
public static void main(String[] args) {
System.gc(); // вызов будет проигнорирован
System.out.println("Вызов проигнорирован");
}
}Вызов проигнорирован
При активированном флаге явные GC-вызовы не приводят к сборке мусора.
Альтернативы в Java
Runtime.getRuntime().gc() – функциональный аналог, реализованный внутри через тот же механизм. Предпочтительнее использовать System.gc() как более краткую форму.
MemoryMXBean.gc() – вызов через JMX-интерфейс. Позволяет инициировать сборку мусора для конкретного пула памяти (например, для Metaspace). Используется в мониторинговых приложениях.
System.runFinalization() – запрос на выполнение финализации объектов, ожидающих финализации. Не заменяет gc, но может быть полезен вместе с ним.
Командная строка и флаги JVM: настройка GC (алгоритм, размеры поколений) влияет на поведение явного вызова. Для большинства приложений лучше положиться на автоматическое управление памятью.
В какой ситуации что предпочесть: в тестах – любой из равнозначных методов; в production-коде явные вызовы обычно не нужны. Для отладки утечек памяти – использование MemoryMXBean или профилировщика.
Аналоги в других языках
PHP: gc_collect_cycles() – принудительный сбор циклических ссылок, возвращает число собранных объектов. Отличие: работает только для циклических ссылок, не гарантирует полную сборку.
$x = new stdClass();
echo gc_collect_cycles(); // 00
JavaScript: нет прямого управления сборкой мусора. Можно использовать WeakRef и FinalizationRegistry для регистрации обратных вызовов при уничтожении объектов.
let ref = new WeakRef({});
// сборка мусора недоступна явно(не применимо)
Python: gc.collect() – принудительный сбор мусора, возвращает число собранных объектов. Может быть вызвано с аргументом поколения.
import gc
a = []
del a
print(gc.collect())2
C#: GC.Collect() – аналогично Java, но с возможностью указать поколение (0,1,2) и режим. Также есть GC.WaitForPendingFinalizers().
GC.Collect();
GC.WaitForPendingFinalizers();(нет вывода)
Lua: collectgarbage() – функция для управления сборкой мусора. Без аргументов запускает полную сборку. Также можно задать режим ("collect", "stop", "restart", "count").
collectgarbage("collect")
print(collectgarbage("count"))12345.67
Go: runtime.GC() – принудительный запуск сборки мусора. Отличие: блокирует выполнение до завершения сбора. Рекомендуется только для тестирования.
import "runtime"
runtime.GC()(нет вывода)
Kotlin: использует ту же JVM, поэтому System.gc() работает идентично. В Kotlin/Native или Kotlin/JS сборка мусора отличается (свои алгоритмы).
Типичные ошибки
Ошибка 1. Предположение, что после вызова все объекты будут немедленно удалены. Реальность: JVM может проигнорировать запрос, если GC не считает нужным запускаться.
System.gc();
// ожидание, что память освобождена
doHeavyAllocation(); // может не хватить памятиOutOfMemoryError (возможен)
Ошибка 2. Частые вызовы в цикле. Приводят к падению производительности из-за частых пауз GC.
while (true) {
System.gc();
// работа
}Высокая загрузка CPU, задержки
Ошибка 3. Использование для освобождения системных ресурсов (файловых дескрипторов, соединений). GC освобождает только память; финализация может вызываться, но не гарантируется. Для ресурсов следует использовать try-with-resources или явное закрытие.
Ошибка 4. Уверенность, что после вызова System.gc() объекты с finalize() будут финализированы. Финализация может быть отложена или пропущена, особенно если объект достижим более одного раза.
Изменения в последних версиях
В JDK 8 и более ранних версиях System.gc() всегда передавал запрос сборщику мусора. Начиная с JDK 9, с появлением модульной системы и улучшений в G1, поведение явного GC стало зависеть от алгоритма:
- G1 GC (по умолчанию с JDK 9) – может проигнорировать явный вызов, если не настроен параметр -XX:+ExplicitGCInvokesConcurrent. Без этого флага вызов может вызвать полную (stop-the-world) сборку, что нежелательно.
- ZGC и Shenandoah – часто игнорируют явные вызовы, так как они спроектированы для минимальных пауз.
- -XX:+DisableExplicitGC (поддерживается с ранних версий) полностью подавляет вызовы.
В JDK 18-21 явных изменений в System.gc() нет, но рекомендации остаются прежними – не полагаться на явные вызовы. В будущих версиях возможно удаление или обесценивание метода (в рамках JEP 421 – Deprecate Finalization).
Расширенные примеры
Пример 1. Совместное использование с MemoryMXBean для мониторинга
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
public class GcMonitor {
public static void main(String[] args) {
MemoryMXBean mmb = ManagementFactory.getMemoryMXBean();
long before = mmb.getHeapMemoryUsage().getUsed();
System.gc();
long after = mmb.getHeapMemoryUsage().getUsed();
System.out.println("Куча до: " + before + ", после: " + after);
}
}Куча до: 1048576, после: 524288
Показывает изменение используемой памяти после запроса.
Пример 2. Сравнение с Runtime.gc() и проверка, что вызовы эквивалентны
public class EquivalenceTest {
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
long start = rt.freeMemory();
System.gc();
long mid = rt.freeMemory();
rt.gc();
long end = rt.freeMemory();
System.out.println("После System.gc: " + (mid - start));
System.out.println("После Runtime.gc: " + (end - mid));
}
}После System.gc: 819200 После Runtime.gc: 0
Разница может быть нулевой, если второй вызов не инициирует сборку (из-за отсутствия необходимости).
Пример 3. Влияние флагов JVM на результат
// Запуск: java -Xms256m -Xmx256m -XX:+UseParallelGC -XX:+DisableExplicitGC GcFlagTest
public class GcFlagTest {
public static void main(String[] args) throws InterruptedException {
byte[][] big = new byte[1024][1024]; // ~1 ГБ, но не поместится
// JVM выбросит OutOfMemoryError, даже если System.gc() вызван
System.gc();
Thread.sleep(1000);
System.out.println("Выжил");
}
}OutOfMemoryError (или 'Выжил' при подходящем размере)
Показывает, что явный GC не спасает от исчерпания памяти, если запрос игнорируется.
Пример 4. Использование в тестовом стенде для очистки состояния между тестами
import org.junit.jupiter.api.Test;
public class GcTest {
@Test
public void testMemoryLeak() {
// подготовка данных
System.gc();
long mem = Runtime.getRuntime().freeMemory();
// выполнение теста
// проверка, что память не утекает
}
}(нет вывода, тест проходит)
Важно: даже при вызове gc, свободная память может не уменьшиться, если объекты всё ещё достижимы.
Пример 5. Сравнение разных поколений GC через GarbageCollectorMXBean
import java.lang.management.ManagementFactory;
import java.lang.management.GarbageCollectorMXBean;
import java.util.List;
public class GcDetails {
public static void main(String[] args) {
List<GarbageCollectorMXBean> beans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean b : beans) {
System.out.println(b.getName() + ": count=" + b.getCollectionCount() + ", time=" + b.getCollectionTime());
}
System.gc();
for (GarbageCollectorMXBean b : beans) {
System.out.println(b.getName() + " after gc: count=" + b.getCollectionCount() + ", time=" + b.getCollectionTime());
}
}
}PS Scavenge: count=2, time=10 PS MarkSweep: count=1, time=20 PS Scavenge after gc: count=2, time=10 PS MarkSweep after gc: count=2, time=35
Видно, что явный вызов вызвал полную сборку (MarkSweep), а сборка молодого поколения (Scavenge) не произошла.