How the JVM Determines Object Liveness and the Role of References and Finalization
This article explains the JVM's object liveness determination methods, covering reference counting and reachability analysis, the four Java reference types, and how finalize() can (but should not) rescue objects, illustrated with code examples and memory‑usage results.
When asked about the JVM, a common question is how to determine an object's liveness. This article describes two main algorithms: reference counting (its drawbacks and why it’s not used in mainstream JVMs) and reachability analysis (the basis of modern GC). It provides a code example of circular references, shows memory usage before and after GC, and explains that the JVM can still collect cyclically referenced objects.
Reference Counting Algorithm
The reference‑counting algorithm increments a counter for each reference to an object and decrements it when a reference is lost. Its advantages are simplicity and high performance, while its disadvantages include CPU overhead, memory consumption for counters, and the inability to handle circular references, which is why mainstream JVMs do not use it.
public class ReferenceDemo {
public Object instance = null;
private static final int _1Mb = 1024 * 1024;
private byte[] bigSize = new byte[10 * _1Mb]; // allocate memory
public static void main(String[] args) {
System.out.println(String.format("Start: %d M", Runtime.getRuntime().freeMemory() / (1024 * 1024)));
ReferenceDemo referenceDemo = new ReferenceDemo();
ReferenceDemo referenceDemo2 = new ReferenceDemo();
referenceDemo.instance = referenceDemo2;
referenceDemo2.instance = referenceDemo;
System.out.println(String.format("Running: %d M", Runtime.getRuntime().freeMemory() / (1024 * 1024)));
referenceDemo = null;
referenceDemo2 = null;
System.gc(); // trigger GC
System.out.println(String.format("End: %d M", Runtime.getRuntime().freeMemory() / (1024 * 1024)));
}
}Start: 117 M Running: 96 M End: 119 M
The result shows that the JVM reclaimed the cyclically referenced objects, confirming that it does not rely on reference counting.
Reachability Analysis Algorithm
Modern JVMs, as well as other languages like C# and Lisp, use reachability analysis. Starting from a set of GC Roots (stack references, JNI references, static fields, etc.), the collector traverses reference chains; objects not reachable are eligible for reclamation. An illustration is included.
Reference Types and Object Liveness
Since JDK 1.2, references are classified as:
Strong Reference – prevents GC while the reference exists.
Soft Reference – cleared only when memory is low, before an OutOfMemoryError.
Weak Reference – cleared at the next GC cycle.
Phantom Reference – cannot retrieve the object; used for post‑mortem cleanup via a reference queue.
These four types illustrate that an object’s liveness is determined by the strength and reachability of its references.
Finalization and Object Rescue
The finalize() method offers a last chance for an object to resurrect itself by re‑establishing a reference during finalization. The following code demonstrates two rescue attempts and the resulting output.
public class FinalizeDemo {
public static FinalizeDemo Hook = null;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Executing finalize method");
Hook = this; // rescue
}
public static void main(String[] args) throws InterruptedException {
Hook = new FinalizeDemo();
// first rescue
Hook = null;
System.gc();
Thread.sleep(500); // wait for finalize
if (Hook != null) {
System.out.println("I am still alive");
} else {
System.out.println("I am dead");
}
// second rescue (same code)
Hook = null;
System.gc();
Thread.sleep(500);
if (Hook != null) {
System.out.println("I am still alive");
} else {
System.out.println("I am dead");
}
}
}Executing finalize method I am still alive I am dead
The output shows that finalize() is called only once, so the second rescue fails. Consequently, using finalize() for object rescue is discouraged because it can run only once, incurs high overhead, is nondeterministic, and offers no guarantee on execution order.
In summary, object liveness in the JVM is determined by reachability from GC Roots, reference types affect collectability, and finalization provides only a limited, unpredictable rescue mechanism.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.