Runtime.gc: примеры (JAVA)
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 для освобождения нативных ресурсов.
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 с ожиданием финализации (для тестов):
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 для детерминированной пост-очистки:
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 -XX:+UseG1GC -Xlog:gc* GcBenchmark
// java -XX:+UseZGC -Xlog:gc* GcBenchmark
// Программа GcBenchmark выполняет циклы выделений и демонстрирует логи GC
[G1 GC log...] [ZGC log...]
Комментарий: логирование помогает понять, как Runtime.gc влияет на конкретный сборщик.
5) Нестандартное применение: освобождение памяти перед загрузкой большого пула данных на слабых устройствах. Несмотря на это, использование должно сопровождаться профилированием и бенчмарками, так как эффекты могут быть противоположными.