Fundamentals 9 min read

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.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
How the JVM Determines Object Liveness and the Role of References and Finalization

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.

JavaJVMGarbage CollectionFinalizeReference Types
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.