What is an object graph in the Java garbage collector?

Learn what is an object graph in the java garbage collector? with practical examples, diagrams, and best practices. Covers java, garbage-collection development techniques with visual explanations.

Understanding the Object Graph in Java Garbage Collection

Hero image for What is an object graph in the Java garbage collector?

Explore what an object graph is in the context of Java's Garbage Collector, how it's formed, and its crucial role in identifying reachable and unreachable objects for memory management.

In Java, memory management is largely handled automatically by the Garbage Collector (GC). A fundamental concept for understanding how the GC works is the 'object graph'. This article delves into what an object graph is, how it's constructed, and its significance in determining which objects are still in use and which can be safely reclaimed.

What is an Object Graph?

An object graph is a conceptual model representing all objects in a Java application's heap memory and the references between them. Think of it as a network where each node is an object, and each directed edge represents a reference from one object to another. This graph is dynamic, constantly changing as objects are created, references are assigned, and objects become eligible for garbage collection.

graph TD
    A[Root Object] --> B[Object 1]
    A --> C[Object 2]
    B --> D[Object 3]
    C --> D
    C --> E[Object 4]
    F[Object 5]
    style F fill:#f9f,stroke:#333,stroke-width:2px
    subgraph Heap Memory
        A
        B
        C
        D
        E
        F
    end

A simplified object graph showing references between objects. Object 5 is unreachable.

The Java Virtual Machine (JVM) maintains a set of 'garbage collection roots' (GC roots). These roots are special references that are always considered reachable and serve as the starting points for traversing the object graph. Any object that can be reached by following a chain of references from a GC root is considered 'reachable' and therefore still in use. Objects that cannot be reached from any GC root are deemed 'unreachable' and are candidates for garbage collection.

Types of GC Roots

Understanding the different types of GC roots is crucial for comprehending how reachability is determined. These roots ensure that essential parts of your application, like active threads and static variables, are never prematurely collected. Here are the primary types of GC roots:

1. Local Variables

References held by local variables in currently executing methods on the stack. As long as a method is active, its local variables are GC roots.

2. Active Threads

All active threads are considered GC roots. Objects referenced by the thread's stack (e.g., local variables, method parameters) are reachable.

3. Static Variables

References held by static fields of loaded classes. These references persist for the lifetime of the class loader.

4. JNI References

References from native code (e.g., C/C++ code accessed via JNI) to Java objects.

5. JVM Internal References

References used by the JVM itself, such as objects used by the class loader or objects held in the symbol table.

How the Garbage Collector Uses the Object Graph

The garbage collection process typically involves a 'mark-and-sweep' or 'mark-and-compact' algorithm (or variations thereof). The object graph is central to the 'mark' phase. The GC starts by identifying all GC roots. From these roots, it traverses the object graph, marking every object it encounters as 'reachable'. Any object that remains unmarked after this traversal is considered unreachable and is then eligible for collection during the 'sweep' phase.

sequenceDiagram
    participant JVM as JVM (GC)
    participant Heap as Heap Memory
    JVM->>Heap: Identify GC Roots
    activate JVM
    loop Traverse Object Graph
        JVM->>Heap: Follow references from roots
        JVM->>Heap: Mark reachable objects
    end
    deactivate JVM
    JVM->>Heap: Identify unmarked (unreachable) objects
    JVM->>Heap: Reclaim memory from unreachable objects

Sequence diagram illustrating the GC's mark phase using the object graph.

public class ObjectGraphExample {
    private static MyObject staticRef;

    public static void main(String[] args) {
        MyObject obj1 = new MyObject("Object 1"); // obj1 is a local variable (GC Root)
        MyObject obj2 = new MyObject("Object 2");
        obj1.setNext(obj2); // obj1 refers to obj2

        staticRef = obj1; // staticRef is a static field (GC Root)

        // obj3 is created and referenced by a local variable, then reference is lost
        MyObject obj3 = new MyObject("Object 3");
        obj3 = null; // Object 3 is now unreachable

        // An object that is never referenced
        new MyObject("Object 4"); // Becomes unreachable immediately after this line

        System.out.println("GC Roots: staticRef, obj1");
        System.out.println("Reachable: obj1, obj2 (via obj1), staticRef");
        System.out.println("Unreachable: obj3, Object 4");

        // Requesting GC (not guaranteed to run immediately)
        System.gc();
    }
}

class MyObject {
    String name;
    MyObject next;

    public MyObject(String name) {
        this.name = name;
        System.out.println(name + " created.");
    }

    public void setNext(MyObject next) {
        this.next = next;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println(name + " finalized.");
    }
}

Java code demonstrating object creation, references, and objects becoming unreachable.

In the example above, staticRef and obj1 in the main method are GC roots. obj2 is reachable because obj1 refers to it. obj3 becomes unreachable when its local reference is set to null. Object 4 is created but never assigned to a strong reference, making it unreachable immediately after its creation line. The finalize() method is overridden to show when an object is about to be garbage collected, though its execution is not guaranteed or timely.