← Назад к вопросам
Как сборщик мусора проверяет, что переменная используется
2.0 Middle🔥 191 комментариев
#JVM и управление памятью#Многопоточность#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как сборщик мусора проверяет, что переменная используется
Сборщик мусора (Garbage Collector) в Java определяет, какие объекты в памяти больше не используются и удаляет их. Это автоматический процесс, критичный для управления памятью. Основной механизм — анализ достижимости объектов через граф ссылок.
Концепция: Reachability (достижимость)
Объект считается достижимым (reachable), если его можно получить, следуя цепочке ссылок из корневых объектов (GC roots). Если объект не достижим — это мусор.
public class Example {
public static void main(String[] args) {
User user1 = new User("John"); // Переменная user1 - корень
User user2 = user1; // Ссылка на тот же объект
user1 = null; // user1 больше не указывает на объект
// Но объект ЕЩЁ достижим через user2
user2 = null; // user2 тоже null
// Теперь объект НЕ ДОСТИЖИМ, GC может удалить
}
}
Корневые объекты (GC Roots)
GC отслеживает ссылки, начиная с этих корневых объектов:
1. LOCAL VARIABLES
├─ Переменные в текущем методе
└─ Переменные в стеке вызовов
2. ACTIVE THREADS
├─ Каждый активный поток
└─ Переменные в его стеке
3. STATIC REFERENCES
├─ Статические переменные класса
└─ Глобальные объекты
4. JNI REFERENCES
├─ Ссылки из Java Native Interface
└─ Объекты, переданные в native код
Алгоритм: Mark and Sweep
Это самый популярный алгоритм сборки мусора:
Шаг 1: MARK (маркировка)
┌──────────────────────────────────────────┐
│ GC root (переменная main) │
│ │ │
│ ▼ │
│ user ──→ User{name="John"} ✓ MARKED │
│ │ │
│ └──→ Address{city="NYC"} ✓ MARKED│
└──────────────────────────────────────────┘
Объекты, на которые НЕ указывают из корней:
- OldUser{"Bob"} ✗ NOT MARKED (мусор)
- OldAddress{} ✗ NOT MARKED (мусор)
Шаг 2: SWEEP (очистка)
┌──────────────────────────────────────────┐
│ Удалить все объекты, которые НЕ маркированы
│ │
│ Памятью удаляются: OldUser, OldAddress │
└──────────────────────────────────────────┘
Пример: как GC отслеживает ссылки
public class GarbageCollectionExample {
public static void main(String[] args) {
// Фаза 1: создание объектов
User user = new User("Alice"); // GC корень: переменная 'user'
Address addr = new Address("NYC"); // GC корень: переменная 'addr'
user.setAddress(addr); // Ссылка через поле
// Фаза 2: "отпущение" адреса
addr = null; // Переменная addr больше не указывает на Address
// НО Address ЕЩЁ достижим через user.address
// Фаза 3: "отпущение" пользователя
user = null; // Теперь user не указывает на User
// Address ТАКЖЕ становится недостижима (удаляется вместе с User)
}
}
// Graph ссылок:
// Начально:
// GC roots:
// user → User{address → Address{...}}
// addr ↗
// После addr = null:
// GC roots:
// user → User{address → Address{...}}
// После user = null:
// GC roots: (ничего)
// ВСЕ объекты могут быть удалены
Отслеживание в разных типах памяти
Heap (кучу, где живут объекты)
public class HeapTracking {
public static void main(String[] args) {
List<String> list = new ArrayList<>(); // list - GC root (в стеке)
list.add("hello"); // "hello" объект в heap, достижим через list
list.add("world"); // "world" объект в heap, достижим через list
list = null; // list больше не указывает на ArrayList
// Оба String объекта и ArrayList БОЛЬШЕ не достижимы
}
}
// Memory layout:
// STACK: | list | ──┐
// └──────┘ │
// HEAP: ▼
// [ArrayList]──→[String "hello"]
// │──→[String "world"]
//
// После list = null:
// STACK: | list=null |
// HEAP: (all objects unreachable)
Stack (где хранятся ссылки локальных переменных)
public void methodA() {
User user = new User(); // Создать объект в heap, ссылка в stack
methodB(user); // Передать ссылку
// После return из methodB, локальная переменная user ещё в стеке
// Объект в heap ещё достижим
}
public void methodB(User user) {
methodC(user); // Передать ссылку
// user из methodB ещё достижим
}
public void methodC(User user) {
// Самая глубокая ссылка
// user в стеке: [main → methodA → methodB → methodC]
}
// Stack после каждого return:
// methodC() вернулась: [main → methodA → methodB]
// methodB() вернулась: [main → methodA]
// methodA() вернулась: [main]
// Если больше нет ссылок — объект удаляется
Определение "используется ли переменная"
GC не смотрит, будет ли переменная использована в БУДУЩЕМ. Он смотрит, ДОСТИЖИМА ли она СЕЙЧАС:
public void example() {
Object obj = new Object(); // Объект создан, достижим
obj = null; // Ссылка обнулена
// Объект БОЛЬШЕ не достижим, даже если дальше идёт код
System.out.println("Объект был удалён GC");
// Это просто не использует obj, но obj уже не существует
}
Граф ссылок в памяти
public class ReferenceGraphExample {
public static void main(String[] args) {
// Граф объектов:
User user = new User("Bob"); // GC root: user
Address addr = new Address(); // GC root: addr
Company company = new Company(); // GC root: company
user.setAddress(addr); // user → addr
company.setManager(user); // company → user
addr.setCountry(country); // addr → country
// Достижимые объекты:
// user (прямой GC root)
// addr (через user)
// company (прямой GC root)
// country (через addr)
}
}
// Граф памяти:
// GC Roots:
// ┌─ user ──→ User ────→ Address ──→ Country
// │ │
// │ └──→ name (String)
// │
// ├─ addr ────→ Address ──→ Country ✓ ДОСТИЖИМ (уже маркирован)
// │
// └─ company ──→ Company ──→ Manager (User) ✓ ДОСТИЖИМ
//
// Недостижимые объекты:
// OldCompany (никто на неё не указывает) ✗
Специальные случаи: Weak References
import java.lang.ref.*;
public class ReferenceTypes {
public static void main(String[] args) {
// 1. Strong reference (обычная ссылка)
Object strong = new Object(); // ДОСТИЖИМА, GC не удалит
// 2. Weak reference (слабая ссылка)
WeakReference<Object> weak = new WeakReference<>(new Object());
Object obj = weak.get(); // Может вернуть null если GC удалил
// Слабая ссылка НЕ препятствует сборке мусора
// 3. Soft reference (мягкая ссылка)
SoftReference<byte[]> cache = new SoftReference<>(new byte[1024]);
// GC удалит, только если памяти критически не хватает
// 4. Phantom reference (фантомная ссылка)
PhantomReference<Object> phantom =
new PhantomReference<>(new Object(), queue);
// Используется только для cleanup
}
}
Как объекты помечаются во время GC
До GC:
Heap: [Object1]✗ [Object2]✓ [Object3]✗ [Object4]✓ ...
Время выполнения GC:
1. Найти GC roots (переменные, потоки, статические поля)
2. Следовать по ссылкам и маркировать достижимые объекты
3. Удалить немаркированные объекты
После GC:
Heap: [Object2]✓ [Object4]✓ ... [пусто] [пусто] ...
↑ ↑
Живые объекты Освобождённая память
Оптимизация: Escape Analysis
Модернные JVM (HotSpot, GraalVM) используют анализ для оптимизации:
public int calculate() {
Point p = new Point(0, 0); // Объект НЕ выходит из метода
p.x = 10;
return p.x;
// HotSpot может ИСКЛЮЧИТЬ создание объекта в памяти
// и работать с переменными прямо в стеке
}
// Оптимизировано эквивалентно:
public int calculate() {
// Object allocation eliminated
int x = 10;
return x;
}
Визуализация работы GC
public class GCVisualization {
public static void main(String[] args) {
// Создание объектов
for (int i = 0; i < 5; i++) {
Object obj = new Object(); // Объект создан
System.out.println("Created " + i);
} // Конец блока - ссылка obj выходит из области видимости
// GC может удалить эти объекты
// Явный запрос GC (не гарантирует выполнение)
System.gc();
}
}
// Что происходит:
// i=0: Create Object → obj references it → exit loop body → obj unreachable
// i=1: Create Object → obj references it → exit loop body → obj unreachable
// i=2: Create Object → obj references it → exit loop body → obj unreachable
// i=3: Create Object → obj references it → exit loop body → obj unreachable
// i=4: Create Object → obj references it → exit loop body → obj unreachable
// System.gc(): Mark objects as garbage, sweep them
Резюме
- GC определяет использование через достижимость: объект используется, если на него можно указать ссылку из GC roots
- GC roots: локальные переменные, активные потоки, статические поля, JNI ссылки
- Mark and Sweep алгоритм: маркировать достижимые объекты, удалить остальные
- Переменная считается неиспользованной, когда все ссылки на её объект удалены
- Weak references не препятствуют сборке мусора
- GC работает автоматически, но можно оптимизировать через Escape Analysis и правильное управление ссылками