← Назад к вопросам

Какие знаешь механизмы, по которым сборщик мусора определяет, какой объект следует удалить?

2.3 Middle🔥 141 комментариев
#JVM и память#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные механизмы определения "мертвых" объектов в сборщике мусора (GC)

В Java/Kotlin (и, соответственно, в Android) сборщик мусора (Garbage Collector, GC) не удаляет объекты напрямую. Его главная задача — определить, какие объекты уже не используются в программе (являются "мертвыми" или "доступными для сборки"), а затем освободить память, занятую этими объектами. Для идентификации таких объектов используются следующие ключевые механизмы.

1. Счетчик ссылок (Reference Counting) — базовый, но не основной в Java/ART

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

Ограничения: Не справляется с циклическими ссылками (объекты ссылаются друг друг на друга, но ни один из них не доступен из "корневого" контекста). Поэтому в основном GC виртуальной машины Android (ART) и JVM он не используется как единственный или основной метод.

// Пример циклической ссылки, проблематичной для простого счетчика
class Node(var next: Node? = null)

fun createCycle() {
    val nodeA = Node()
    val nodeB = Node()
    nodeA.next = nodeB
    nodeB.next = nodeA // Счетчики ссылок обоих объектов будут > 0, даже если они "недоступны"
}

2. Алгоритмы трассировки (Tracing Algorithms) — основной подход

Это семейство алгоритмов, которые активно используются в современных GC (включая ART в Android). Они работают путем "трассировки" (прохождения) графа объектов, начиная с корневых ссылок (GC Roots).

GC Roots — это особые точки, гарантированно доступные и живые. К ним относятся:

  • Статические поля классов.
  • Активные потоки (Thread) и их локальные переменные в стеке.
  • Локальные переменные в стеке вызовов методов (Java-фреймы).
  • Ссылки из нативной (JNI) части кода.
  • Системные классы и константы.

Основные алгоритмы трассировки:

  • Mark-and-Sweep (Пометка и очистка):
    1. Фаза пометки (Mark): GC начинает с корней и рекурсивно проходит все достижимые объекты, помечая их как "живые".
    2. Фаза очистки (Sweep): GC проходит всю память (кучу) и удаляет все объекты, не помеченные как живые.
// В упрощенном представлении алгоритм работает с графом объектов
// Все, что не достижимо из корней (Roots), будет удалено.
  • Mark-and-Sweep с оптимизациями: Часто дополняется другими фазами для уменьшения фрагментации памяти (например, компактизация (Compaction) — перемещение живых объектов для создания непрерывного свободного блока памяти).

  • Трассировка поколений (Generational Tracing): Основана на гипотезе, что большинство объектов живут очень мало ("молодые" объекты), а долгоживущие объекты редко становятся мусором. Память разделяется на молодое поколение (Young Generation, часто с Eden и Survivor spaces) и старое поколение (Old Generation/Tenured). Сборка в молодом поколении (Minor GC) происходит часто и быстро, а в старом (Major GC/Full GC) — реже, но требует больше времени. ART использует эту модель с дополнительными оптимизациями.

3. Алгоритм поиска достижимости (Reachability Analysis) — суть трассировки

Фактически, это общее название для процесса, лежащего в основе трассировки. Объект считается доступным для сборки мусора (garbage-collectible) если и только если нет пути достижимости от любой корневой ссылки (GC Root) до этого объекта.

Сильные и слабые ссылки в Java/Kotlin расширяют эту концепцию:

  • Сильные ссылки (Strong References) — обычные ссылки. Объект живет, пока существует хотя бы одна сильная ссылка на него из достижимого контекста.
  • Слабые (Weak), Фоновые (Soft) и Фиктивные (Phantom) ссылки — специальные типы, которые не препятствуют сборке мусора (или препятствуют в меньшей степени, как SoftReference). Они позволяют объектам быть достижимыми через особые ReferenceQueue, но GC может удалить объект, даже если на него есть такие ссылки, что используется в кешировании и избегает утечек памяти.
import java.lang.ref.WeakReference

// Пример слабой ссылки: объект может быть удален GC, даже если weakRef существует
val strongObject = Any()
val weakRef = WeakReference(strongObject)

// Если мы удалим сильную ссылку, объект станет кандидатом на сборку
strongObject = null // Теперь единственная ссылка на объект — слабая (weakRef)
// При следующем запуске GC объект, скорее всего, будет удален, и weakRef.get() вернет null

Особенности на Android (ART)

Механизмы определения мусора в ART (Android Runtime) развиты и оптимизированы для мобильных устройств:

  • Используется Generational GC с разделением на молодое и старое поколение.
  • Введены гибридные подходы, такие как одновременная (Concurrent) и частично параллельная сборка для уменьшения пауз (Stop-the-World).
  • Начиная с Android 8.0 (Oreo), ART использует сборщик на основе профилирования (Profile-guided GC), который анализирует поведение приложения в фоне и оптимизирует планирование сборок для улучшения производительности и времени работы батареи.

Таким образом, основной механизм определения "мертвых" объектов в Android — это трассировка достижимости от корневых ссылок, реализованная через сложные, оптимизированные алгоритмы (Mark-and-Sweep, Generational), учитывающие специфику мобильных приложений и управление различными типами ссылок для предотвращения утечек памяти.

Какие знаешь механизмы, по которым сборщик мусора определяет, какой объект следует удалить? | PrepBro