What is an object graph in the Java garbage collector?
Categories:
Understanding the Object Graph in Java Garbage Collection

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.
System.gc()
or finalize()
for critical resource management is generally discouraged. System.gc()
is merely a hint to the JVM, and finalize()
has performance overheads and unpredictable execution times. Prefer try-with-resources or explicit resource closing.