← Назад к вопросам
Как Garbage collector понимает, что объект можно уничтожить
1.7 Middle🔥 181 комментариев
#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как Garbage Collector понимает, что объект можно уничтожить
Основной механизм: Reachability Analysis
Гарбейж коллектор определяет, что объект можно удалить через анализ достижимости (reachability analysis):
- Объект можно удалить, если он недостижим из GC root'ов
- GC root — это объекты, которые JVM сохраняет всегда
- Если нет цепи ссылок из root'ов к объекту — объект удалён
GC Root'ы
Это точки входа для анализа:
1. Локальные переменные текущих потоков
└── int x; String s; List<Order> orders;
2. Статические переменные классов
└── public static final Logger LOG;
└── public static Configuration config;
3. JNI ссылки (из нативного кода C++)
└── jobject obj;
4. Method area (метаданные ClassLoader'ов)
└── Информация о классах
5. Thread stack — стек каждого потока
└── Все локальные переменные
Mark-and-Sweep алгоритм
Гарбейж коллектор работает в 2 фазы:
Фаза 1: MARK (Пометь)
1. Начните с GC root'ов
2. Используйте DFS (Depth-First Search) для обхода
3. Помечаем каждый достижимый объект
Пример:
Root (Stack):
├─ list (ArrayList) ← помечаем
│ └─ item1 (String) ← помечаем
│ └─ item2 (String) ← помечаем
│
├─ config (Config) ← помечаем
│ └─ database (DB) ← помечаем
│
└─ unused (Data) ← НЕ помечаем
└─ temp (String) ← НЕ помечаем
Фаза 2: SWEEP (Подмести)
1. Пройти по всем объектам на heap
2. Если объект не помечен → удалить
3. Освободить память
Результат:
До SWEEP: После SWEEP:
┌──────────┐ ┌──────────┐
│ item1 │ ✓ │ item1 │
├──────────┤ ├──────────┤
│ item2 │ ✓ │ item2 │
├──────────┤ ├──────────┤
│ database │ ✓ │ database │
├──────────┤ ├──────────┤
│ unused │ ✗ → │ [empty] │
├──────────┤ ├──────────┤
│ temp │ ✗ → │ [empty] │
└──────────┘ └──────────┘
Пример: Как GC определяет удаление
public class GCDemo {
public static void main(String[] args) {
// === Этап 1: Создание объектов ===
// Strong reference
List<String> list = new ArrayList<>();
list.add("data1");
list.add("data2");
// ✅ ArrayList и String достижимы через root list
Map<String, Object> config = new HashMap<>();
config.put("key", "value");
// ✅ HashMap и String достижимы через root config
// Бесполезный объект
String unused = "this will be gc'd";
// ✅ String достижим через root unused
// === Этап 2: Удаляем ссылки ===
unused = null;
// ❌ String больше не достижим
// GC может удалить на следующем цикле
list = null;
// ❌ ArrayList и его содержимое больше не достижимы
config = null;
// ❌ HashMap и его содержимое больше не достижимы
// === Этап 3: Запуск GC ===
System.gc(); // Просьба к JVM запустить GC
// GC выполнит:
// 1. MARK: помечает объекты, которые ещё достижимы
// 2. SWEEP: удаляет непомеченые (unused, list, config)
}
}
Проблема: Circular Reference (Циклическая ссылка)
class Node {
Node next;
byte[] data = new byte[1024];
}
public class CircularDemo {
public static void main(String[] args) {
Node node1 = new Node();
Node node2 = new Node();
// Создали циклическую ссылку
node1.next = node2;
node2.next = node1; // node1 ← node2 ← node1
// Удаляем strong reference
node1 = null;
node2 = null;
// ❌ node1 и node2 ссылаются друг на друга!
// Но оба недостижимы из root'ов
System.gc();
// ✅ GC удалит обоих
// (GC способен ловить циклические ссылки через graph tracing)
}
}
GC Roots vs Обычные объекты
public class RootsDemo {
// === GC ROOTS ===
// 1. Статические переменные — это root!
static List<String> staticList = new ArrayList<>();
public static void main(String[] args) {
// 2. Локальные переменные main — root!
String localVar = "I am root";
List<Order> orders = new ArrayList<>();
// Даже если orders = null в конце main,
// эта переменная была root, пока main выполнялась
}
// === ОБЫЧНЫЕ ОБЪЕКТЫ ===
public void someMethod() {
// Локальная переменная — root только внутри этого метода
String temp = "temporary";
// Когда someMethod() вернёт управление,
// temp уже не является root
}
}
Виды GC Collectors
Разные GC алгоритмы, но все используют reachability:
G1GC (Garbage First)
├── Делит heap на регионы
├── Помечает регионы с мусором
└── Собирает регионы с наибольшей плотностью мусора
ZGC (Zero GC)
├── Очень быстрый (pause < 10ms)
├── Использует цветные указатели
└── Помечает во время выполнения программы
Shenandoah
├── Низкие паузы
├── Concurrent marking и compaction
└── Работает параллельно с приложением
Как отследить удаление объектов
public class GCTracking {
public static void main(String[] args) throws Exception {
// Создаём объект со своим finalize
class Traceable {
String name;
Traceable(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("Garbage collecting: " + name);
super.finalize();
}
}
Traceable obj1 = new Traceable("obj1");
Traceable obj2 = new Traceable("obj2");
obj1 = null; // obj1 становится garbage
System.gc();
Thread.sleep(100); // Ждём finalization
// Вывод: "Garbage collecting: obj1"
obj2 = null; // obj2 становится garbage
System.gc();
Thread.sleep(100);
// Вывод: "Garbage collecting: obj2"
}
}
Важно: finalize() deprecated и больше не используется. Используй try-with-resources вместо.
На собеседовании
"Garbage Collector понимает, что объект можно уничтожить через анализ достижимости (reachability analysis):
- Начинает с GC root'ов (локальные переменные потоков, статики)
- Обходит граф объектов (DFS) и помечает все достижимые объекты
- Все непомеченые объекты считаются мусором
- Удаляет мусор и освобождает память
Это называется Mark-and-Sweep алгоритм.
Пример: если переменная = null, объект становится недостижим и будет удалён на следующем GC цикле.
Даже циклические ссылки удаляются, потому что GC смотрит на граф в целом, а не на отдельные ссылки."