The Object Lifecycle in Java

From new to unreachable: how objects are created, how long they live, and what actually happens when nothing references them any more.

The stages

  1. Creation - memory is allocated and the object is initialised.
  2. Use - the object is reachable through at least one reference.
  3. Unreachable - no live reference remains.
  4. Collection - the garbage collector reclaims the memory, at a time it chooses.

Creation, step by step

Note note = new Note("Java basics");
  1. The class is loaded and initialised, if it has not been already.
  2. Memory is allocated on the heap.
  3. All fields are set to their default values.
  4. The superclass constructor runs, up the chain to Object.
  5. Field initialisers and instance initialiser blocks run, in source order.
  6. The constructor body runs.
  7. The reference is assigned to the variable.
class Base {
    Base() { System.out.println("2 Base constructor"); }
}

class Derived extends Base {
    private String value = init();          // 3
    { System.out.println("4 instance block"); }

    static { System.out.println("1 static block, once per class"); }

    Derived() {
        super();                            // implicit if omitted
        System.out.println("5 Derived constructor");
    }

    private String init() {
        System.out.println("3 field initialiser");
        return "ready";
    }
}

Reachability

Note a = new Note("first");
Note b = a;          // one object, two references

a = null;            // still reachable through b
b = null;            // now unreachable, eligible for collection

An object becomes eligible for collection when no chain of references from a GC root reaches it. Roots include local variables of live threads, static fields and active thread objects.

GC roots
  |
  +--> [ Service ] --> [ Cache ] --> [ Entry ]     reachable, kept
  |
       [ OldNote ] --> [ Detail ]                  unreachable island, collected
Two objects referring to each other are still collected if nothing outside reaches them. Java uses reachability tracing, not reference counting, so cycles are not a problem.

Scope and lifetime are different things

List<Note> all = new ArrayList<>();

void add() {
    Note note = new Note("kept");   // the variable ends with the method
    all.add(note);                  // the object survives, the list references it
}

A local variable disappears at the end of its block. The object it referred to lives as long as anything still points at it.

What garbage collection guarantees

  • It reclaims memory for unreachable objects.
  • It does not promise to run at any particular moment.
  • It does not release files, sockets or database connections.
System.gc();   // a request, not a command; the JVM may ignore it

Releasing resources properly

try (BufferedReader reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}   // close() is called automatically, in reverse order, even on an exception

Anything holding an operating system resource should implement AutoCloseable and be used with try with resources. This is deterministic, unlike collection.

finalize is gone, and Cleaner is rarely needed

The old finalize() method was deprecated and then removed, because it ran unpredictably, could resurrect objects and delayed collection. For the rare case of a safety net over a native resource, java.lang.ref.Cleaner exists. It is not a substitute for closing resources explicitly.

Generations, in one paragraph

Most objects die young, so the heap is usually split into a young generation and an old one. New objects are allocated in the young generation and collected there cheaply; survivors are promoted. This is why creating many short lived objects is far less costly than intuition suggests.

Common mistakes

  • Believing System.gc() collects immediately.
  • Relying on collection to close a file or a connection.
  • Holding objects in a long lived static collection and never removing them, which is the most common Java memory leak.
  • Registering a listener and never removing it.
  • Assuming null assignment frees memory at once. It only removes one reference.

Best practices

  • Keep references only as long as the object is genuinely needed.
  • Use try with resources for anything closeable.
  • Remove entries from long lived caches, or use a cache with an eviction policy.
  • Prefer immutable objects, which are simpler to reason about and easy for the collector.
  • Do not write code that depends on when collection happens.

Practice

  1. Predict the printed order of the Derived example, then run it.
  2. At which line does the object created in new Note("first") become eligible for collection?
  3. Explain why two objects that reference each other are still collected.
  4. Rewrite a manual close() in a finally block as try with resources and say what improved.
  5. Describe a realistic scenario where a static Map causes a memory leak, and how to fix it.

Conclusion

An object lives as long as something reachable refers to it, and no longer. Let the collector handle memory, close resources yourself, and watch long lived collections, because that is where objects overstay.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.