How does the JVM decide an object is garbage? Explain reachability and GC roots.
answer
- Reachable from roots = live; unreachable = garbage
- Roots: stack locals, static fields, JNI refs, live threads
- Reachability, NOT reference counting → cycles are collected
- Java 'leak' = still reachable from a root (e.g. growing static collection)
basics
~20 sAn object is garbage when it can no longer be reached by following references from "GC roots" (like local variables and static fields). The collector keeps reachable objects and frees the rest. It is not based on reference counting.
solid answer
~50 sHotSpot uses reachability, not reference counting, to decide what is garbage. The GC starts from a set of GC roots — references the program can definitely still touch: local variables and operands on each thread's stack, active method parameters, static fields of loaded classes, JNI references, and live thread objects. From those roots it traverses the object graph (a mark phase); every object reachable through some chain of references is live, and everything unreachable is garbage and can be reclaimed. Because it is reachability-based, cyclic structures (A points to B, B points to A) with no external reference are still collected — which is why Java does not leak cycles the way naive reference counting would. "Memory leaks" in Java are really objects still reachable from a root (e.g. a static collection that keeps growing), so they are never eligible for collection.
go deeper
Knows GC frees objects you no longer use and that 'no longer used' roughly means nothing points to it.
Explains reachability from GC roots, names the main roots, and contrasts it with reference counting (cycles are collected).
Articulates the mark traversal, distinguishes a Java leak (still reachable from a root) from true garbage, and names common leak sources and weak-reference fixes.
Reasons about root-set scanning cost, safepoints, conservative vs precise roots, and how reachability semantics underpin the ref-type ladder, finalization ordering, and leak-diagnosis tooling (heap dumps, dominator trees).
## The question GC must answer **Garbage collection (GC)** is the JVM automatically freeing memory of objects you no longer need. But how does it *know* you no longer need an object? It cannot read your intent — so it uses a precise, mechanical rule: **an object is garbage if your program can no longer reach it.** ## Two ways to define "can't reach it" — and which Java uses ### Reference counting (NOT what HotSpot uses) One approach: give each object a counter of how many references point to it; when the count hits zero, free it. Simple, but it has a fatal flaw: **cycles**. If object A references B and B references A, but nothing else references either, both counters stay at 1 forever — they leak. So mainstream JVMs do **not** use reference counting. ### Reachability (what HotSpot uses) Instead, the GC asks: *starting from places the program can definitely still access, can I walk references and arrive at this object?* If yes → **live (reachable)**. If no → **garbage (unreachable)**. This naturally handles cycles: a self-referencing island that nothing outside points to is unreachable from the roots, so it gets collected. ## GC roots — the starting points A **GC root** is a reference the running program is guaranteed to be able to use *right now*, so anything reachable from it must be kept. The main roots are: - **Stack references**: local variables and operand-stack entries in every **active stack frame** of every thread (method parameters and `this` included). If a method currently has a variable pointing at an object, that object is obviously still in use. - **Static fields**: `static` fields of loaded classes are reachable through the class, which lives as long as its classloader does. (This is the classic accidental-leak source — a static `List` that you keep adding to.) - **JNI references**: objects held by native code through the **Java Native Interface**. - **Live threads**: running `Thread` objects and their thread-locals. - A few JVM-internal roots (e.g. classes being initialized, monitors held). ## The traversal (mark phase) The collector performs a graph traversal: 1. Put all GC roots into a worklist. 2. **Mark** each as live, then follow every reference field it holds to other objects, marking those, and so on (a breadth/depth-first walk of the **object graph**). 3. When the worklist empties, every object reached is **live**; every unmarked object is **garbage**. 4. The collector then reclaims the garbage (by sweeping, or by copying survivors and freeing the rest — depends on the collector). This is the core of **mark-and-sweep / mark-copy / mark-compact** algorithms. ## What this means for "memory leaks" in Java Java can still "leak": a leak here means **an object stays reachable from a root even though you logically don't need it anymore**, so GC correctly (from its view) keeps it. Common causes: a growing `static` cache/collection, listeners never unregistered, `ThreadLocal`s not cleared on pooled threads. The fix is to **drop the reference** (so the object becomes unreachable) — e.g. remove from the collection, or use a `WeakReference`-based structure so the GC can reclaim it when only weakly reachable. ## Tie-in to this topic The `java.lang.ref` reference types refine reachability into a ladder — **strongly / softly / weakly / phantom** reachable — which only makes sense once you understand that plain reachability from roots is what keeps objects alive. `Cleaner` and finalization run *after* an object becomes unreachable.
- Why doesn't a cycle of mutually-referencing objects leak in Java?Because collection is reachability-based, not count-based. If no GC root can reach the cycle, the whole island is unreachable and gets collected together, regardless of the internal references it holds.
- Give a concrete example of a Java memory leak despite GC.A static List (or HashMap cache) you keep adding entries to but never remove. The static field is a GC root, so every entry stays reachable and is never collected, growing the heap until OutOfMemoryError.
saying these in an interview costs you the question
- Claiming the JVM uses reference counting
- Saying cyclic references cause leaks in Java GC (they don't)
- Thinking setting a variable to null always frees memory immediately (it only removes one reference; GC runs later, and other roots may still reach it)
- Believing Java cannot have memory leaks