System.gc: примеры (JAVA)

Примеры вызова System.gc и анализ поведения
Раздел: Управление памятью (сборка мусора, System.gc)
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(); // 0
0

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 для мониторинга

Пример java
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() и проверка, что вызовы эквивалентны

Пример java
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
// Запуск: 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. Использование в тестовом стенде для очистки состояния между тестами

Пример java
import org.junit.jupiter.api.Test;
public class GcTest {
    @Test
    public void testMemoryLeak() {
        // подготовка данных
        System.gc();
        long mem = Runtime.getRuntime().freeMemory();
        // выполнение теста
        // проверка, что память не утекает
    }
}
(нет вывода, тест проходит)

Важно: даже при вызове gc, свободная память может не уменьшиться, если объекты всё ещё достижимы.

Пример 5. Сравнение разных поколений GC через GarbageCollectorMXBean

Пример java
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) не произошла.

джава System.gc function comments

En
System.gc Runs the garbage collector