Question 25 · Section 3

What are GC roots?

4. Handshakes (Java 11+) → fewer global STW 5. Minimize thread count

Language versions: English Russian Ukrainian

Junior Level

GC Roots — “root” objects from which GC starts searching for live objects.

Simple analogy: A tree. Roots are GC Roots. If a branch (object) can be reached from a root → it’s alive.

What are GC Roots:

  • Local variables in methods
  • Static fields of classes
  • Active threads
  • JNI references

Rule: Object can be reached from GC Root → object is alive.


Middle Level

Full Classification

GC Roots:
├── Stack Locals (local variables)
├── Static Fields (static fields)
├── Thread Objects (active threads)
├── JNI Global (global native references)
├── JNI Local (local native references)
├── Monitor Used (objects in synchronized)
└── JVM Internal (system objects)

OopMaps

OopMaps (Ordinary Object Pointer Maps): JIT creates a map where each byte of
code specifies which local variables contain references. Without OopMaps, GC
would have to scan every byte of the stack — slow, plus false positives
(normal int values that accidentally look like pointers).
  → At Safepoints, JIT knows which registers/offsets = references
  → Root Scanning in milliseconds

Path to GC Roots (MAT)

MAT: Path to GC Roots
  → exclude soft/weak/phantom → strong references only
  → Shows who's holding the object

→ 99% of leaks = strong references from Static or Thread

Senior Level

Root Scanning and Latency

Number of Roots affects pauses:
  → 1000 threads → long stack scanning
  → Millions of keys in static HashMap

Modern Java (Handshakes):
  → Per-thread scanning
  → Minimized global pause

JNI Global Reference Leaks

Native code created global reference:
  → NewGlobalRef(obj)
  → Forgot DeleteGlobalRef

→ Object won't be GC'd!
→ "Invisible" leak

MAT shows JNI Global References as GC Roots. They appear as
"JNI Global" in Path to GC Roots view. But JNI Local References (references
in native code) are not visible to MAT — this is a "blind spot".

Finalizer Queue

Objects with finalize():
  → After death, go to Finalizer Queue
  → Become temporary GC Root
  → Until finalize() executes → object alive

→ Delayed removal
→ Reference Handler thread processes queue

Best Practices

  1. Monitor Root Scanning time in GC logs
  2. JNI → DeleteGlobalRef mandatory
  3. exclude weak/soft in MAT
  4. Handshakes (Java 11+) → fewer global STW
  5. Minimize thread count

Senior Summary

  • GC Roots = foundation of Reachability Analysis
  • OopMaps = fast scanning
  • JNI Global = invisible leaks
  • Root Scanning time → indicator of problems
  • Path to GC Roots = main MAT tool
  • Finalizer Queue = temporary GC Root

Interview Cheat Sheet

Must know:

  • GC Roots — root objects from which GC starts reachability traversal: Stack Locals, Static Fields, Thread Objects, JNI Global, JNI Local, Monitor Used, JVM Internal
  • OopMaps: JIT creates a map where each byte of code specifies which variables contain references; without OopMaps, GC would scan every byte (slow + false positives)
  • Path to GC Roots (MAT): exclude soft/weak/phantom → strong references only; 99% of leaks = references from Static or Thread
  • JNI Global Reference leaks: NewGlobalRef(obj) without DeleteGlobalRef → object won’t be GC’d; MAT shows as “JNI Global” in Path to GC Roots
  • Finalizer Queue: objects with finalize() become temporary GC Root after death; until finalize() executes → object alive
  • Root Scanning time: number of Roots affects pauses; 1000 threads → long stack scanning; Handshakes (Java 11+) minimize global pause

Common follow-up questions:

  • Why are OopMaps important for performance? — Without OopMaps, GC scans every byte of stack → slow + false positives (int that accidentally looks like pointer)
  • How does JNI Global Reference cause a leak? — Native code created global reference and forgot to delete; GC sees it as GC Root → object never deleted
  • Why is Finalizer Queue a temporary GC Root? — Object with finalize() goes to Finalizer Queue after death; until finalize() executes → object alive → removal delay
  • What is Root Scanning time in GC logs? — Time to scan all GC Roots; if growing → too many threads or static references

Red flags (DO NOT say):

  • “GC Roots are only static fields” — also includes Stack Locals, Thread Objects, JNI references, Monitor objects
  • “JNI leaks are visible in Heap Dump” — JNI Global visible, but JNI Local (in native code) is MAT “blind spot”
  • “finalize() is fast and safe” — object alive until finalize() executes; Reference Handler thread processes queue asynchronously

Related topics:

  • [[5. When does an object become eligible for GC]]
  • [[26. What is reachability in the context of GC]]
  • [[4. What is Garbage Collection]]
  • [[21. What is a memory leak and how to detect it]]
  • [[27. Can you manually invoke GC]]