Back to JVM Internals: Advanced
Q3Question 3 of 10

What are GC roots and how does tracing work?

Key Concepts
  • GC roots are starting points for reachability analysis: local variables, static fields, active threads, JNI references
  • Objects reachable from GC roots are LIVE – everything else is garbage
  • The GC traces from roots, marks reachable objects, then reclaims the rest
  • This is why setting a reference to null makes the object eligible for GC (if no other references exist)
  • Common GC roots: thread stacks, static fields, system class loader, JNI global refs

Definition

GC roots are the starting points of reachability analysis. Objects reachable from roots are live. Everything else is garbage to be collected.

GC root types:

  • Thread stacks: Local variables and parameters of all running methods.
  • Static fields: Class-level references in loaded classes.
  • Active threads: Thread objects themselves.
  • JNI references: Native code references.
  • System classes: Classes loaded by the bootstrap class loader.

GC roots are like the bank's list of active accounts. Starting from active accounts (roots), the bank traces all linked accounts (references). Accounts not reachable from any active account are considered dormant and archived (garbage collected).

Understanding the Concepts

How tracing works

DIAGRAM
  GC Roots:
    Thread stack: main() → local var 'rajesh' → Account@0x100
    Static field: TransferService.instance → TransferService@0x200

  Tracing:
    Account@0x100 → balance (BigDecimal@0x300) → LIVE
    TransferService@0x200 → repository (Repository@0x400) → LIVE

  Not reachable from any root:
    Account@0x500 (no references) → GARBAGE → will be collected

What makes an object eligible for GC?

JAVA
Account a = new Account("SAV2001");
a = null;  // no more references to Account@0x100
// Account@0x100 is now unreachable → eligible for GC

// But:
Account a = new Account("SAV2001");
Account b = a;  // two references
a = null;  // b still references Account@0x100
// Account@0x100 is still reachable via b → NOT eligible

What about circular references?

JAVA
class Node {
    Node next;
}
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a;  // circular reference

a = null;
b = null;
// Both nodes are unreachable from any GC root
// Java GC handles circular references correctly – both are collected
// (Unlike reference counting in Python/Objective-C where circular refs leak)

What should a senior engineer know?

  • Heap dumps show which GC roots keep objects alive. Use jmap -dump:live + Eclipse MAT.
  • Memory leaks happen when an unintended GC root (e.g., a static collection) keeps growing.
  • Soft/Weak references do not count as strong reachability – they allow GC.

What are common mistakes?

A common mistake is not understanding the concept fully before using it in production banking applications.

Wrong approach:

JAVA
package com.bank.service;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, double amount) {
        // WRONG: using double for money
        double balance = getBalance(fromAccount);
        balance = balance - amount;
    }
    private double getBalance(String id) { return 10000.0; }
}

Correct approach:

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        // CORRECT: using BigDecimal for money
        BigDecimal balance = getBalance(fromAccount);
        balance = balance.subtract(amount);
    }
    private BigDecimal getBalance(String id) { return new BigDecimal("10000.00"); }
}

Always use BigDecimal for monetary calculations in banking systems to avoid floating-point precision errors.

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  GC Roots → trace references → mark LIVE objects
  Everything NOT marked → GARBAGE → reclaimed

  Roots: thread stacks, static fields, active threads, JNI
  Circular refs: both collected if no root reaches either

  Memory leak: unintended root keeps objects alive
  Debug: heap dump + Eclipse MAT → find the retaining path

Key Takeaway

GC roots are the starting points for determining which objects are live. The GC traces all references from roots, marks reachable objects, and reclaims everything else. Circular references are handled correctly – if neither object is reachable from a root, both are collected.

One sentence to remember: an object is live if reachable from a GC root, garbage if not – roots are thread stacks, static fields, and active threads.

---

Share
Use ← → arrow keys to navigate