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

Вызов сборщика мусора через Runtime.gc в Java
Раздел: Управление памятью (сборка мусора, System.gc)
Runtime.gc: void

Общее описание Runtime.gc

Метод Runtime.gc() вызывается для запроса запуска сборщика мусора в среде выполнения Java. Это обращение является рекомендацией JVM выполнить сборку мусора, а не гарантией ее немедленного или полного выполнения. Аналогичный статический вызов доступен через System.gc(), который делегирует вызов к Runtime.

Подпись метода: public void gc(). Параметров у метода нет, возвращаемого значения тоже нет (void).

Типичные сценарии использования включают попытки освободить память перед выполнением большого по объему выделения, до измерений используемой памяти, а также для тестов и отладки поведения сборщика мусора при включенных логах GC. При этом следует учитывать, что поведение метода зависит от реализации сборщика мусора, флагов JVM и настроек безопасности.

Важные замечания:

  • Вызов служит только как запрос. JVM может проигнорировать его; в некоторых конфигурациях явные вызовы GC могут быть отключены (см. раздел о флагах JVM).
  • Метод не возвращает статистику и не сообщает об успешности освобождения конкретных объектов.
  • Ресурсы, отличные от управляемой памяти (файловые дескрипторы, сокеты), не освобождаются автоматически; для них следует применять явное закрытие или java.lang.ref.Cleaner / try-with-resources.

Простые примеры вызова gc и измерения эффекта

Пример 1. Простой вызов и измерение доступной памяти до и после.

public class GcExample1 {
    public static void main(String[] args) {
        Runtime rt = Runtime.getRuntime();
        long before = rt.freeMemory();
        byte[] b = new byte[10 * 1024 * 1024]; // 10 MB
        long afterAlloc = rt.freeMemory();
        b = null; // кандидат на сборку
        rt.gc();
        long afterGc = rt.freeMemory();
        System.out.println("before=" + before);
        System.out.println("afterAlloc=" + afterAlloc);
        System.out.println("afterGc=" + afterGc);
    }
}
before=12345678
afterAlloc=3456789
afterGc=6789012

Результаты зависят от JVM; показан типичный сценарий уменьшения свободной памяти при выделении и частичного восстановления после GC.

Пример 2. Вызов через System.gc() и подключение логов GC при запуске:

public class GcExample2 {
    public static void main(String[] args) {
        System.gc();
        System.out.println("Requested GC");
    }
}
Requested GC
[GC (System.gc())  100K->40K(512K), 0.005ms]

Лог GC появляется только при включенных флагах JVM, например -Xlog:gc или старом варианте -verbose:gc.

Альтернативные подходы и похожие API в Java

Несколько механизмов и API предоставляют более контролируемые или безопасные способы работы с жизненным циклом объектов:

  • System.gc() - статический эквивалент вызова Runtime; разница минимальна, предпочтение чаще определяется стилем кода.
  • Runtime.runFinalization() - предлагает запуск финализации объектов; не рекомендуется полагаться на finalize, который помечен как устаревший.
  • java.lang.ref.WeakReference / SoftReference / PhantomReference - позволяют построить более предсказуемую логику очистки и освобождения ресурсов.
  • java.lang.ref.Cleaner - современная альтернатива finalize для выполнения очистки нативных ресурсов.
  • GarbageCollectorMXBean и MemoryMXBean из java.lang.management - для получения статистики и мониторинга, а не непосредственного управления GC.

Выбор зависит от цели: для мониторинга и диагностики предпочтительнее MXBean и логирование GC; для управления очисткой нативных ресурсов - Cleaner или Reference-API; для простого запроса сборки - System.gc()/Runtime.gc(), с ожиданием, что JVM решит, когда реально выполнять сборку.

Аналоги в других языках и их особенности

Короткое сравнение с примерами:

  • Python: модуль gc, функция gc.collect(). Возвращает количество собранных объектов. Пример:
import gc
collected = gc.collect()
print('collected', collected)
collected 0

Особенность: в Python доступна информация о числе собранных объектов и можно управлять порогами сборки.

  • C# (DotNet): GC.Collect() и GC.WaitForPendingFinalizers(). Позволяет указать поколения и опции. Пример:
using System;
class P { static void Main(){ GC.Collect(); Console.WriteLine("Requested GC"); }}
Requested GC

В .NET сборка также может быть принудительной и поддерживает более тонкие опции по поколениям.

  • Go: runtime.GC(). Выполняет сборку мусора немедленно в текущем потоке. Пример:
package main
import ("runtime"; "fmt")
func main(){ runtime.GC(); fmt.Println("GC called") }
GC called

Отличие: в Go вызов предлагает более детерминированное поведение по сравнению с Java.

  • Node.js / JavaScript: в V8 GC не доступен по умолчанию; при запуске с --expose-gc можно вызвать global.gc(). Пример:
// node --expose-gc script.js
if (global.gc) { global.gc(); console.log('gc'); } else { console.log('no gc'); }
gc

Особенность: необходимость специального запуска, иначе явный вызов отсутствует.

  • PHP: функция gc_collect_cycles() для удаления циклических ссылок. Пример:
<?
var_dump(gc_collect_cycles());
?>
int(0)

Вывод: у многих языков есть способ запросить GC, но семантика и степень гарантии различаются. Java предоставляет только рекомендацию, тогда как некоторые среды (Go, .NET) дают более явный контроль.

Типичные ошибки и заблуждения

  • Ожидание немедленного освобождения памяти. Пример:
public class WrongExpectation {
    public static void main(String[] args) {
        Object o = new byte[50_000_000];
        o = null;
        Runtime.getRuntime().gc();
        // Ожидание: память сразу освободится. На практике: JVM может не собрать сразу
        System.out.println("После gc");
    }
}
После gc

Комментарий: метод лишь просит GC; поведение зависит от реализации и настроек JVM.

  • Полагание на finalize(). Пример:
public class FinalizeExample {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize called");
    }
    public static void main(String[] args) {
        new FinalizeExample();
        System.gc();
    }
}
(может не вывести 'finalize called')

Комментарий: finalize устарел и не должен использоваться для освобождения критичных ресурсов.

  • Запуск в горячем цикле и ухудшение производительности: частые вызовы gc в цикле приводят к задержкам и деградации пропускной способности.
  • Игнорирование флагов JVM: при включенном -XX:+DisableExplicitGC системные явные вызовы GC игнорируются. Пример запуска: java -XX:+DisableExplicitGC MyApp.

Изменения и примечания по версиям JVM и Java

Сам метод Runtime.gc() как API не претерпел существенных изменений: подпись и поведение (как рекомендации JVM) сохранились. Значительные изменения касаются среды выполнения:

  • Депрекация Object.finalize() с Java 9 и планами по удалению в будущих версиях; это снижает смысл опираться на финализацию при использовании gc.
  • Появление и развитие различных сборщиков мусора: G1, Shenandoah, ZGC и другие. Они по-разному реагируют на явные запросы GC и обладают различными гарантиями пауз.
  • Добавлены и изменены флаги JVM для управления поведением явных вызовов GC, в том числе -XX:+DisableExplicitGC и тонкая настройка через -XX-параметры.

Рекомендация: при миграции на новые версии JVM проверить особенности выбранного сборщика и влияние явных вызовов GC на производительность.

Расширенные сценарии применения и нетипичные примеры

1) Использование Cleaner вместо finalize для освобождения нативных ресурсов.

Пример java
import java.lang.ref.Cleaner;
class ResourceHolder {
    private static final Cleaner cleaner = Cleaner.create();
    private static class State implements Runnable { /* нативный дескриптор */
        public void run(){ System.out.println("Cleaning native resource"); }
    }
    private final State state;
    private final Cleaner.Cleanable cleanable;
    ResourceHolder(){ state = new State(); cleanable = cleaner.register(this, state); }
}
public class CleanerExample{
    public static void main(String[] args){
        new ResourceHolder();
        System.gc();
    }
}
Cleaning native resource

Комментарий: Cleaner обеспечивает явную и безопасную очистку без использования finalize.

2) Мониторинг и принудительный GC с ожиданием финализации (для тестов):

Пример java
public class WaitForFinalizers {
    public static void main(String[] args) {
        Object o = new Object() { protected void finalize(){ System.out.println("finalized"); } };
        o = null;
        System.gc();
        try { Thread.sleep(200); } catch (InterruptedException ignored) {}
    }
}
finalized (может или не может появиться)

Комментарий: использование ожиданий ненадежно; лучше применять тестовые фреймворки и mock-обёртки вместо зависимости от GC.

3) Использование ReferenceQueue и PhantomReference для детерминированной пост-очистки:

Пример java
import java.lang.ref.*;
public class PhantomExample {
  public static void main(String[] args) throws InterruptedException{
    ReferenceQueue q = new ReferenceQueue<>();
    byte[] data = new byte[10_000_000];
    PhantomReference pr = new PhantomReference<>(data, q);
    data = null;
    System.gc();
    Reference<?> ref = q.poll();
    System.out.println(ref == null ? "not enqueued" : "enqueued");
  }
}
enqueued

Комментарий: PhantomReference позволяет получить уведомление о готовности объекта к очистке и выполнить завершающие действия независимо от finalize.

4) Сравнение поведения разных сборщиков (пример запуска):

Пример java
// Запуск для сравнения
// java -XX:+UseG1GC -Xlog:gc* GcBenchmark
// java -XX:+UseZGC -Xlog:gc* GcBenchmark
// Программа GcBenchmark выполняет циклы выделений и демонстрирует логи GC
[G1 GC log...] 
[ZGC log...]

Комментарий: логирование помогает понять, как Runtime.gc влияет на конкретный сборщик.

5) Нестандартное применение: освобождение памяти перед загрузкой большого пула данных на слабых устройствах. Несмотря на это, использование должно сопровождаться профилированием и бенчмарками, так как эффекты могут быть противоположными.

джава Runtime.gc function comments

En
Runtime.gc Runs the garbage collector