skip to content

When does an object become eligible for garbage collection in Java, and what makes it eligible?

level: middleimportance: must knowfreq 72%

answer

  1. eligible = unreachable from any GC root
  2. roots: stack locals, statics, threads, JNI
  3. out of scope / reassign / null = lose last ref
  4. cycles still collected (tracing, not refcount)
  5. eligible ≠ collected now
  6. leak = unintended reachability (static maps, caches)

basics

~20 s

An object becomes eligible for garbage collection when it is no longer reachable from any live part of the running program — nothing holds a reference to it anymore. Setting all references to it to null or letting them go out of scope makes it unreachable.

solid answer

~50 s

An object is eligible for garbage collection when it becomes **unreachable** — there is no chain of references from any GC root (live local variables on stack frames, static fields, JNI references, active threads) to that object. Common ways this happens: a reference variable goes out of scope, you reassign it to point elsewhere, or you set it to `null` and no other reference remains. Eligibility is about reachability, not about when the object is actually freed — the JVM reclaims eligible objects at some later, unspecified time when the collector runs. Note that two objects referencing only each other (an island/cycle) are still eligible if nothing reachable points into the island, because Java uses tracing/reachability collection, not reference counting. Memory leaks in Java are typically *unintended reachability*: a long-lived collection or static map that keeps references alive.

code

java · 9 lines
java
Object a = new Object();
Object b = a;        // two refs to one object
a = null;            // still reachable via b
b = null;            // now unreachable -> eligible for GC

// Island of isolation (cycle) is still eligible:
Node x = new Node(), y = new Node();
x.next = y; y.next = x;
x = null; y = null;  // both eligible despite referencing each other

go deeper

for a junior

Knows an object is collectable when nothing references it anymore (out of scope or set to null).

for a middle

Explains reachability from GC roots, the three ways references are lost, and that eligibility is not the same as immediate collection.

for a senior

Discusses tracing vs reference counting (cycles), System.gc() being a hint, and how unintended reachability causes leaks; knows reference types (weak/soft/phantom) exist.

for a principal

Reasons about collector algorithms (generational, region-based like G1/ZGC), allocation patterns, GC tuning trade-offs, and designing for low garbage / weak-reference caches.

## What garbage collection is **Garbage collection (GC)** is the JVM's automatic memory management: it finds objects the program can no longer use and reclaims their heap memory so it can be reused. You never call `free`/`delete` in Java; the GC does it for you. The key question is: *which objects can be reclaimed?* The answer is **unreachable** objects. ## Reachability and GC roots The collector starts from a set of **GC roots** — references that are definitely "live": - **Local variables** in the stack frames of currently executing methods (and method parameters). - **Static fields** of loaded classes. - **Active threads** and thread-local references. - **JNI** (native code) references. From these roots, the collector follows every reference, then every reference from those objects, and so on — transitively marking everything it can reach. Any object **not** reached by this traversal is **unreachable** and therefore **eligible for garbage collection**. So: *an object is eligible for GC exactly when no chain of references leads to it from any GC root.* ## How objects become unreachable ```java Dog d = new Dog(); // reachable via local var d d = null; // the Dog is now unreachable (if no other ref) -> eligible ``` ```java void f() { Dog d = new Dog(); } // when f() returns, d's frame is gone -> Dog eligible ``` ```java Dog a = new Dog(); Dog b = new Dog(); a = b; // the first Dog is now unreachable -> eligible ``` Three triggers, all the same underlying cause — **the last reference disappears**: going out of scope, reassignment, or explicit `null`. ## Eligibility vs. actual collection Becoming *eligible* does **not** mean the memory is freed *now*. The JVM frees eligible objects at an unspecified later time, when a collection cycle runs (driven by allocation pressure and the chosen collector). `System.gc()` is only a *hint* and may be ignored. So you can never rely on an object being collected at a precise moment. ## Cycles: why reachability beats reference counting ```java a.next = b; b.next = a; // a and b reference each other a = null; b = null; // nothing reachable points into the pair ``` Even though `a` and `b` still reference each other, the whole **island** is unreachable from any root, so both are eligible. A naive **reference-counting** scheme would wrongly keep them (each has count 1). Java uses **tracing/reachability** collection, which correctly reclaims cycles. ## Memory leaks in Java GC eliminates dangling pointers and most leaks, but not all. A **leak** in Java means *unintended reachability*: an object you're done with is still reachable, so it's never eligible. Classic causes: a `static` collection or cache that keeps growing, listeners never unregistered, `ThreadLocal`s on pooled threads. The fix is to drop references (remove from the collection, null it out, deregister). ## finalize / Cleaner (brief) Don't rely on `finalize()` (deprecated) or `Cleaner` for timely cleanup — they run, if ever, at GC's discretion. Use `try-with-resources`/`AutoCloseable` for deterministic release of external resources (files, sockets).

  • If two objects reference each other but nothing else references them, are they eligible for GC?
    Yes. Java uses tracing (reachability) collection, so an isolated cycle unreachable from any GC root is eligible. Reference counting would incorrectly keep it alive.
  • Does calling `System.gc()` guarantee an eligible object is collected immediately?
    No. `System.gc()` is only a hint; the JVM may delay or ignore it. Eligibility and actual reclamation are decoupled.

Think of objects as people connected by phone numbers, and GC roots as the few people the company actually pays. Anyone you can reach by a chain of calls from a paid employee stays. A clique of friends who only call each other but whom no employee can reach (an island) gets cut — even though they're still calling each other.

saying these in an interview costs you the question

  • Saying an object is collected the moment its reference is nulled
  • Claiming Java uses reference counting / that cycles leak
  • Believing `System.gc()` forces immediate collection
  • Confusing eligibility (reachability) with the act of freeing memory
  • Thinking `finalize()` is reliable cleanup

context