🏗️ Classes & Objects · Intermediate

Object lifetime & garbage collection in Java

Unreachable objects are collected automatically; no destructors; finalize is deprecated.

🧩 The mysteryIn C, every byte you allocate must be freed by hand. In Java, you've created thousands of objects and never deleted a single one. So where did they all go?

No delete in Java

Java has **no delete, no free and no destructors. A garbage collector** (GC) automatically reclaims the memory of objects your program can no longer use.

Reachability is everything

The GC starts from roots — local variables of running methods, static fields — and follows references. Anything it can't reach is garbage, *eligible* for collection. When it's actually collected is up to the JVM; System.gc() is only a hint.

Car c = new Car("A");
c = null;  // Car A: unreachable now
🤔 Think first

Which car is garbage?

Car x = new Car("X"); Car y = x; x = new Car("Z"); y = null; — which Car objects are eligible for garbage collection?

Think about it, then reveal the answer

Only X. At first both x and y point to X. Then x moves to the new Z, and y = null drops the last reference to X. Z is still reachable through x.

Cycles are no problem

Two objects that reference each other, but that nothing else can reach, are still garbage. Java traces from the roots instead of counting references, so an isolated cycle is collected — pure reference counting would leak it.

⚠️ The trap

finalize() is a trap

finalize() may run late or never, and it's deprecated for removal (JEP 421, Java 18). Don't rely on it to close files or connections. Use try-with-resources to release them deterministically.

try (var in = Files.newInputStream(p)) {
    // use in
} // closed here, guaranteed

Leaks still happen

The GC frees only unreachable objects. If a reachable collection keeps references you no longer need — say a static list that only grows — nothing is ever freed, and eventually you hit OutOfMemoryError. That's a Java memory leak.

static List<byte[]> cache = new ArrayList<>();
// cache.add(...) forever -> OOM
💼 In the real world

In real projects

Production memory leaks are almost always "forgotten but reachable": caches without limits, listeners never removed, sessions never expired. Engineers find them with heap dumps, and tune collectors like G1 (the default) or ZGC for low pause times.

Key takeaways

  1. Unreachable objects are collected automatically
  2. Collection time is up to the JVM, not you
  3. Cycles of unreachable objects are collected too
  4. Reachable-but-forgotten objects still leak memory
🤯 Did you know?

ZGC, one of the JVM's garbage collectors, is designed to keep pause times under a millisecond — even with heaps of many terabytes.

Practice questions

When does an object become eligible for garbage collection?

  1. When no live thread can reach it through any chain of references
  2. Immediately when its variable goes out of scope
  3. Only when System.gc() is called
  4. When its reference count drops to exactly zero
Check your answer

When no live thread can reach it through any chain of references. Reachability, not scope or counting, is what matters. The GC traces from roots like local variables and static fields; anything it cannot reach is garbage.

After these lines run, which Car objects are eligible for garbage collection?

Car a = new Car("A");
Car b = new Car("B");
a = b;
b = null;
  1. Only Car A
  2. Only Car B
  3. Both cars
  4. Neither car
Check your answer

Only Car A. a = b drops the only reference to Car A. b = null clears one reference to Car B, but a still points to it, so B is reachable.

Next: println(myDog) prints something like Dog@1b6d3586. What is that gibberish?