Питання 25 · Розділ 3

Що таке GC roots?

4. Handshakes (Java 11+) → менше STW 5. Мінімізуйте кількість потоків

Мовні версії: English Russian Ukrainian

🟢 Junior Level

GC Roots — це «кореневі» об’єкти, від яких GC починає пошук живих об’єктів.

Проста аналогія: Дерево. Корені — це GC Roots. Якщо до гілки (об’єкта) можна дістатися від кореня → вона жива.

Що є GC Roots:

  • Локальні змінні в методах
  • Статичні поля класів
  • Активні потоки
  • JNI посилання

Правило: До об’єкта можна дістатися від GC Root → об’єкт живий.


🟡 Middle Level

Повна класифікація

GC Roots:
├── Stack Locals (локальні змінні)
├── Static Fields (статичні поля)
├── Thread Objects (активні потоки)
├── JNI Global (глобальні нативні посилання)
├── JNI Local (локальні нативні посилання)
├── Monitor Used (об'єкти в synchronized)
└── JVM Internal (системні об'єкти)

OopMaps

OopMaps (Ordinary Object Pointer Maps): JIT створює карту, де в кожному байті
коду вказано, які локальні змінні містять посилання. Без OopMaps GC
довелося б сканувати кожен байт стеку — повільно, плюс хибні спрацьовування
(звичайні int-значення, випадково схожі на вказівники).
  → У Safepoints JIT знає, які регістри/зміщення = посилання
  → Root Scanning за мілісекунди

Path to GC Roots (MAT)

MAT: Path to GC Roots
  → exclude soft/weak/phantom → тільки сильні посилання
  → Показує, хто тримає об'єкт

→ 99% витоків = сильні посилання від Static або Thread

🔴 Senior Level

Root Scanning і Latency

Кількість Roots впливає на паузи:
  → 1000 потоків → довге сканування стеків
  → Мільйони ключів у static HashMap

Modern Java (Handshakes):
  → Сканування потоків поодинці
  → Мінімізація глобальної паузи

JNI Global Reference витоки

Нативний код створив глобальне посилання:
  → NewGlobalRef(obj)
  → Забув DeleteGlobalRef

→ Об'єкт не видалиться GC!
→ «Невидима» витік

MAT показує JNI Global References як GC Roots. Вони відображаються як
"JNI Global" у Path to GC Roots view. Але JNI Local References (посилання
в native-коді) MAT не бачить — це «сліпа зона».

Finalizer Queue

Об'єкти з finalize():
  → Після смерті потрапляють у Finalizer Queue
  → Стають тимчасовим GC Root
  → Поки finalize() не виконається → об'єкт живий

→ Затримка видалення
→ Reference Handler потік обробляє чергу

Best Practices

  1. Моніторьте Root Scanning time у GC логах
  2. JNI → DeleteGlobalRef обов’язково
  3. exclude weak/soft у MAT
  4. Handshakes (Java 11+) → менше STW
  5. Мінімізуйте кількість потоків

Резюме для Senior

  • GC Roots = фундамент Reachability Analysis
  • OopMaps = швидке сканування
  • JNI Global = невидимі витоки
  • Root Scanning time → індикатор проблем
  • Path to GC Roots = головний інструмент MAT
  • Finalizer Queue = тимчасовий GC Root

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • GC Roots — кореневі об’єкти, від яких GC починає обхід досяжності: Stack Locals, Static Fields, Thread Objects, JNI Global, JNI Local, Monitor Used, JVM Internal
  • OopMaps: JIT створює карту, де в кожному байті коду вказано, які змінні містять посилання; без OopMaps GC сканував би кожен байт (повільно + хибні спрацьовування)
  • Path to GC Roots (MAT): exclude soft/weak/phantom → тільки сильні посилання; 99% витоків = посилання від Static або Thread
  • JNI Global Reference витоки: NewGlobalRef(obj) без DeleteGlobalRef → об’єкт не видалиться GC; MAT показує як «JNI Global» у Path to GC Roots
  • Finalizer Queue: об’єкти з finalize() після смерті стають тимчасовим GC Root; поки finalize() не виконається → об’єкт живий
  • Root Scanning time: кількість Roots впливає на паузи; 1000 потоків → довге сканування стеків; Handshakes (Java 11+) мінімізують глобальну паузу

Часті уточнюючі запитання:

  • Чому OopMaps важливі для продуктивності? — Без OopMaps GC сканує кожен байт стеку → повільно + хибні спрацьовування (int, випадково схожий на вказівник)
  • Як JNI Global Reference викликає витік? — Нативний код створив глобальне посилання і забув видалити; GC бачить його як GC Root → об’єкт ніколи не видалиться
  • Чому Finalizer Queue — тимчасовий GC Root? — Об’єкт з finalize() після смерті потрапляє у Finalizer Queue; поки finalize() не виконається → об’єкт живий → затримка видалення
  • Що таке Root Scanning time у GC логах? — Час сканування всіх GC Roots; якщо росте → занадто багато потоків або статичних посилань

Червоні прапорці (НЕ говорити):

  • «GC Roots — це тільки static поля» —还包括 Stack Locals, Thread Objects, JNI посилання, Monitor об’єкти
  • «JNI витоки видно у Heap Dump» — JNI Global видно, але JNI Local (в native-коді) — «сліпа зона» MAT
  • «finalize() швидкий і безпечний» — об’єкт живий, поки finalize() не виконається; Reference Handler потік обробляє чергу асинхронно

Пов’язані теми:

  • [[5. Коли об’єкт стає кандидатом на видалення GC]]
  • [[26. Що таке reachability в контексті GC]]
  • [[4. Що таке Garbage Collection]]
  • [[21. Що таке витік пам’яті і як його виявити]]
  • [[27. Чи можна вручну викликати GC]]