πŸ“Œ Definition

  • GC Roots are the starting points that the JVM garbage collector uses to discover all reachable objects in the heap.
  • Any object reachable from a GC Root is considered alive.
  • Objects not connected to any root are considered garbage and are eligible for collection.

🧩 Categories of GC Roots

  1. Local Variables and Parameters (Thread Stacks)

    • Each thread has its own call stack.

    • Local variables and method parameters in stack frames can hold references to heap objects.

    • GC scans these stacks to collect roots.

    • πŸ”‘ Example:

      void run() {
          Person p = new Person("Jonas"); // p is a root during run()
      }
  2. Active Threads

    • Each java.lang.Thread object representing a running thread is treated as a root.
    • Keeps the thread object and its execution state alive.
  3. Static Fields

    • Classes loaded by the JVM have static fields stored in class metadata.

    • These references are treated as roots for as long as the class is loaded.

    • πŸ”‘ Example:

      class Config {
          static Database db = new Database(); // db is a root
      }
  4. JNI Handles (Native References)

    • When Java interacts with native code via JNI, native code can hold references.
    • Stored in JNI Global Handle Tables managed by the JVM.
    • These pinned references act as GC roots.
  5. Special JVM Internals

    • Class loaders, system classes, exception tables, synchronization monitors, and other runtime structures.
    • These are maintained directly by the JVM’s C++ runtime.

πŸ” How GC Roots Are Found

To put it simply, in the section before, we have different kinds of roots, and the way they are discovered by the GC are different for each of them.

  • Class Metadata Tables

    • Each loaded class has metadata stored in Metaspace (Java 8+) or PermGen (Java 7-).
    • Static fields are accessible via these tables.
  • JNI Global Handles

    • The JVM keeps dedicated tables for native references.
    • GC walks these tables to gather roots.
  • Thread Control Blocks (TCBs)

    • JVM maintains per-thread metadata including stack pointers.
    • GC queries TCBs to start walking each stack.
  • Thread Stacks β†’ OopMaps

    • The JIT compiler generates OopMaps at safepoints (bytecode locations where GC may occur).
    • OopMaps tell the GC exactly which slots in a frame contain references vs primitives.
    • Prevents mistakes (e.g., treating integers as references).

πŸ› οΈ Root Discovery Process

  1. Stop-the-World (or reach safepoints): JVM ensures threads are paused or at safe scanning positions.
  2. Enumerate Roots:
    • Collect all references from stacks, statics, JNI, and internal runtime structures.
  3. Build Root Set:
    • Roots are put into a temporary work queue.
  4. Mark Phase:
    • Traverse the object graph from the root set, marking everything reachable as alive.

πŸ“Š Text-Based Diagram

 GC Roots
   β”œβ”€β”€ Thread Objects (java.lang.Thread)
   β”‚     └── Call Stacks
   β”‚           β”œβ”€β”€ Local Variables β†’ Heap Objects
   β”‚           └── Parameters β†’ Heap Objects
   β”‚
   β”œβ”€β”€ Static Fields β†’ Heap Objects
   β”‚
   β”œβ”€β”€ JNI Global Handles β†’ Heap Objects
   β”‚
   └── JVM Internals β†’ ClassLoaders, System Structures

🧠 Why GC Roots Matter

  • Defines the β€œalive set”: Only objects reachable from roots can still be used.
  • Cycle safety: Even if two objects reference each other, if neither is reachable from a root, both are collectible.
  • Performance: Efficient root scanning reduces pause times.

🌐 Quick Analogy

  • Think of GC Roots as the β€œmain roads” in a city.
  • If you can reach a building (object) by traveling from a main road, it’s still β€œin use.”
  • If a building is cut off from all roads, nobody can reach it β€” so it can be demolished (garbage collected).

πŸ“ Key Takeaways

  • GC Roots are not stored in one array β†’ they live in multiple runtime structures.
  • The JVM knows exactly where they are (stacks, metadata, JNI tables, internal structures).
  • GC starts traversal from these roots to discover all live objects.
  • Roots guarantee program correctness: anything directly used by threads, statics, or the runtime is kept alive.