Когда инициируется сборка мусора


В языках программирования с автоматическим управлением памятью, таких как Java, C#, Python, сборка мусора (Garbage Collection, GC) — это механизм освобождения памяти, занимаемой объектами, которые больше не используются программой. Сборщик мусора ищет такие объекты и удаляет их, возвращая ресурсы операционной системе. Однако момент и условия инициации сборки мусора зависят от конкретной реализации и среды выполнения (runtime).

📌 Что инициирует сборку мусора

Сборка мусора не происходит строго по расписанию. Вместо этого она инициируется системой в зависимости от ряда факторов:

✅ 1. Недостаток свободной памяти (memory pressure)

Это основной триггер. Когда хип (куча памяти) заполняется, а новая память для объектов не может быть выделена, сборщик мусора запускается автоматически.

Пример:

  • Программа создает множество объектов.

  • Куча заполняется.

  • JVM или CLR инициирует GC, чтобы освободить неиспользуемую память.

✅ 2. Явный вызов сборки мусора (не гарантирует её запуск)

Во многих языках можно попросить запустить GC вручную. Однако это не гарантирует, что GC будет запущен немедленно — это всего лишь запрос.

Java:

System.gc(); // Рекомендует JVM запустить GC

C#:

GC.Collect(); // Запрашивает сборку мусора

Python:

import gc
gc.collect() # Принудительный вызов GC

Система сама решает, запускать ли сборку, исходя из текущего состояния памяти.

✅ 3. Переход между фазами исполнения (например, простои приложения)

Сборка мусора может запускаться в момент, когда приложение временно не активно (idle time), чтобы минимизировать влияние на производительность.

✅ 4. В зависимости от поколения объектов (в языках с поколениями GC)

Современные сборщики мусора (в Java, .NET) используют поколенческую модель, где объекты делятся на:

  • Молодое поколение (Young) — сюда попадают новые объекты.

  • Старое поколение (Old/ Tenured) — выжившие после нескольких сборок.

  • Промежуточные поколения — в зависимости от конкретной реализации.

GC может запускаться отдельно для каждого поколения:

  • Minor GC — собирает только молодое поколение (дешёвая операция).

  • Major (Full) GC — включает также старое поколение (более дорогая).

Пример:

В Java:

  • Minor GC запускается часто при создании новых объектов.

  • Full GC запускается реже, при нехватке памяти или старении объектов.

✅ 5. Ограничения и параметры среды выполнения (runtime flags)

Программист или администратор может влиять на поведение сборщика мусора с помощью параметров конфигурации:

Java (JVM):

  • -Xms и -Xmx — начальный и максимальный размер кучи.

  • -XX:+UseG1GC — выбор типа сборщика (G1, ZGC и др.).

  • Параметры, влияющие на пороги поколений и частоту GC.

.NET:

  • Можно контролировать поведение GC через GCSettings, Server GC, Workstation GC.

  • В .NET Core можно использовать Span<T>, stackalloc и другие механизмы, уменьшающие давление на GC.

✅ 6. Детерминированное освобождение (в сочетании с GC)

Хотя GC управляет памятью, время освобождения ресурсов (например, файлов, сокетов) всё равно должно контролироваться вручную. Для этого используются:

В C#:

using (var file = new StreamReader("file.txt")) {
// Файл будет закрыт при выходе из using
}

В Java:

try (FileInputStream fis = new FileInputStream("file.txt")) {
// Автоматически вызывается close()
}

GC не управляет освобождением неуправляемых ресурсов, для этого применяют try-with-resources, using, финализаторы или AutoCloseable.

📌 Что считается "неиспользуемым объектом"?

Объект считается достижимым (reachable), если на него есть хотя бы одна активная ссылка из:

  • стека вызовов (локальные переменные),

  • глобальных переменных (static),

  • полей других живых объектов.

Если нет ни одной цепочки ссылок — объект считается собираемым мусором.

📌 Алгоритмы и подходы к сборке мусора

Разные языки используют разные алгоритмы:

  • Tracing GC — отслеживает достижимые объекты, удаляя остальные (Java, .NET).

  • Reference Counting (подсчёт ссылок) — каждый объект хранит счётчик (Python частично, Objective-C).

  • Mark-and-Sweep — пометка и удаление.

  • Generational GC — разделение на поколения.

  • Concurrent GC — выполняется параллельно с программой.

📌 Частота сборки мусора

GC не запускается на каждом шаге. Он балансирует между:

  • частотой запусков,

  • временем простоя приложения,

  • количеством освобождённой памяти.

📌 Специфика в некоторых языках

🔹 Java

  • Использует поколенческую модель.

  • Существует множество алгоритмов GC: Serial, Parallel, CMS, G1, ZGC, Shenandoah.

🔹 .NET (C#)

  • Поддерживает фоновые и принудительные GC.

  • Использует поколения: 0, 1, 2.

🔹 Python

  • Комбинирует подсчёт ссылок и сборку циклических ссылок через модуль gc.

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