Как сборщик мусора понимает, что объект можно уничтожить?

Сборщик мусора (Garbage Collector, GC) в Java определяет, что объект можно уничтожить, если на него невозможно больше сослаться из кода программы — то есть если он недостижим (unreachable) от так называемых корневых ссылок (GC Roots).

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

📌 Как GC понимает, что объект "мертвый"

🔗 1. Анализ достижимости от GC Roots

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

💡 Что такое GC Roots?

GC Roots — это объекты, доступные всегда, даже если на них не ссылаются другие объекты:

  • Ссылки из стека текущих потоков (локальные переменные, параметры методов)

  • Ссылки из статических полей (static)

  • Ссылки из **JNI (native code)
    **

  • Ссылки из **менеджеров классов
    **
  • Ссылки из синхронизационных структур (мониторы, блокировки)

✅ Пример

public class Test {
public static void main(String\[\] args) {
Person p = new Person("Alice");
p = null;
}
}

Здесь:

  1. Переменная p в стеке указывает на объект Person.

  2. После p = null объект всё ещё в куче, но больше нет ссылок на него.

  3. При следующем проходе GC обнаружит, что объект недостижим, и удалит его.

🔄 Граф достижимости (Reachability Graph)

GC строит дерево/граф:

GC Root
|
\[objA\]  \[objB\]  \[objC\]

Если objA достижим из GC Root, а objB и objC достижимы через него, они сохраняются. Если objA теряет связь с GC Root — вся цепочка может быть уничтожена.

🧠 Специальные уровни достижимости

Java 9+ (и раньше, в java.lang.ref):

  1. Strong Reference — обычная ссылка (Person p = new Person()): объект жив до обнуления этой переменной.

  2. Soft Reference — объект удаляется только при нехватке памяти.

  3. Weak Reference — объект удаляется при первом проходе GC, если нет других strong-ссылок.

  4. Phantom Reference — используется для отслеживания уничтожения объектов (без доступа к ним).

🔥 Циклические ссылки — НЕ проблема

GC умеет обнаруживать циклы. Даже если два объекта ссылаются друг на друга, но при этом они не достижимы от GC Roots, они будут удалены.

class A {
B b;
}
class B {
A a;
}

Если ни A, ни B не доступны из GC Roots — цикл будет удалён.

🔍 Как GC это делает?

Зависит от типа сборщика мусора:

  • Mark-and-Sweep:

    • Mark — проходит от GC Roots и помечает все достижимые объекты.

    • Sweep — всё непомеченное удаляется.

  • Generational GC (у большинства JVM):

    • Молодые объекты (в Eden) собираются чаще.

    • Старые объекты (Old Gen) — реже.

  • G1, ZGC, Shenandoah — делают это параллельно и инкрементально, избегая долгих пауз.

📦 Когда объект действительно уничтожается?

  • GC определяет, что объект не достижим.

  • Вызывается (опционально) его метод finalize() (до Java 9; позже deprecated).

  • Объект удаляется физически и память возвращается в кучу.

📌 Важно помнить

  • GC никогда не гарантирует немедленного удаления объекта после потери ссылки.

  • Вызывать System.gc() — только в исключительных случаях, это необязательная рекомендация для GC.

  • Если объекты "живут слишком долго", возможна утечка памяти — когда ссылки существуют, но объекты уже не нужны (например, кэш, статическое поле, слушатель событий).

Таким образом, сборщик мусора считает объект пригодным к удалению тогда, когда на него нельзя попасть, начиная от GC Roots. Именно достижимость, а не "использовался ли объект недавно", определяет судьбу объекта в куче.