Вопрос 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. Что такое memory leak и как его обнаружить]]
  • [[27. Можно ли вручную вызвать GC]]