GC Roots in Java: How to Use Them in Memory Analysis

Memory Analyzer Reports contain various sections such as Overview, Dominator Tree, Class Histogram, Threads, Duplicate Classes, Unreachable Objects, System Properties, OQL… In this post, let’s learn about the GC Roots section. What information is present in this GC Roots? What patterns should you look for in GC Roots? What actions can you take directly from this view? and more importantly How to use this GC Roots to troubleshoot Memory problems?

What are GC Roots?

A Root is responsible for keeping the tree alive. Similarly GC Roots are the references which keeps the objects alive in memory. Garbage Collectors start scanning from the GC roots to identify the active objects in the memory. The objects that are not accessible through the chain of references from the GC roots are considered to be eligible for garbage collection.

What are the types of GC Roots?

Common types of GC Roots are:

1. Local Variables: A local variable is declared inside a method or a block of code. While a method is executing, the JVM must preserve all objects referenced by its local variables to avoid disrupting program logic. For this reason, local variables are treated as GC Roots.

Example:

public void processData() {
    Data data = new Data(); // 'data' is a local variable in this method
    data.process();         // JVM needs to keep 'data' alive while this method is running
}

In the above code, ‘data’ is the local variable. As long as ‘processData()’ is running, the ‘Data’ object it refers to is not eligible for garbage collection. What JVM does at this point is, it treats ‘data’ as a GC Root, and begins the reachability analysis during the garbage collection process.

2. Static Variables:  A static variable belongs to the class itself rather than any specific instance.The moment the class is loaded, the static variable gets created, and it remains in memory as long as the class is loaded. Since these static variables hold references to objects, the JVM treats them as GC roots.

Example:

public class AppState {
    private static Config globalConfig = new Config(); // 'globalConfig' is a static variable
}

The ‘globalConfig’ is a static variable, in the above example. As long as the ‘AppState’ class is loaded, the object is considered live, and it won’t be collected by the garbage collector, since the static variable is treated as a GC Root.

3. Active Threads:  An active thread is one that is currently running or waiting to run. Since threads have their own call stacks and can reference objects, the JVM marks all active threads as GC Roots to ensure any objects they’re using remain accessible.

Example:

Thread t = new Thread(() -> {
    Logger logger = new Logger();
    logger.log("Thread started");
});
t.start();

In the above code, as soon as thread t is started, it begins running in the background. The logger object is referenced inside the thread’s ‘run()’ method, so it has to remain reachable through the thread’s call stack and cannot be garbage collected.

4. JNI References (Native Code): Native code can hold references to Java objects, the Java code interacts with the native code through Java Native Interface (JNI). Since these references are not visible to the JVM directly, they are treated as GC Roots, ensuring that the objects are not collected by mistake. 

Example:

A C++ library might use NewGlobalRef(env, obj) to retain a Java object reference across calls. That object is treated as live until the native code explicitly deletes the reference.

In such cases, even if the object isn’t referenced anywhere in Java, the JVM keeps it alive because the native code might access it.

5. System Class Loader References: Core classes in Java are loaded by the bootstrap or system class loaders. These class loaders stay alive as long as the application keeps running, and so any objects reachable from the classes they load are also alive. Hence they are considered as GC Roots. 

Example:

Class<?> clazz = Class.forName("java.util.HashMap");

The HashMap Class is loaded by the system class loader in the above code. So any static references inside the class will remain reachable and are not garbage collected. 

6. Monitors: When a thread enters a ‘synchronized’ block or method, it locks on a specific object, which is called a monitor. The JVM would mark such objects as GC Roots to make sure the synchronization mechanism functions correctly. 

Example:

synchronized(lockObj) {
    // Critical section
}

In this code, the object ‘lockObj’ is being used as a monitor. As long as the thread holds the lock, the JVM considers ‘lockObj’ as in use and prevents it from being garbage collected.

GC Roots in Memory Analyzer Report

Memory Analyzer tools like HeapHero provide a dedicated section in its analysis report to show information about GC Roots.

Fig: Default view of the GC Roots section

Fig: Drill-down view into a specific GC Root

There’s two images here and the first one shows the default view of the GC Roots section. Also, this image lists out all the top-level GC roots that are currently present in the memory. You even have the capability to click on these entries to explore further. That brings you to the second figure, which shows a detailed view of one of the GC Roots—in this case, the ‘main’ thread.

On the right side of the GC Root table, you’ll see the ‘Actual Data’ panel, which includes the following tabs:

  • Attributes: You can see the list of all member variables of the object referenced by the GC Root.
  • Statics: This tab shows all static variables associated with the object.
  • Value: This content contains the toString() output of the object that is being used for a quick view. 
  • Overview: This section presents the general metadata such as Object ID, Shallow Size, Retained Size, and more.

Conclusion

From what we’ve seen so far, we can all come to a conclusion that GC Roots are important for the diagnosis of memory retention issues. They are the basis for the JVM’s memory reachability analysis as well. With the help of the GC root analysis, we get to trace why certain objects remain alive, and they also allow you to detect the hidden memory leak issues. If you are looking to fix such issues, then we recommend using the GC roots view to identify these anchors. 

Share your Thoughts!

Up ↑

Index

Discover more from HeapHero – Java & Android Heap Dump Analyzer

Subscribe now to keep reading and get access to the full archive.

Continue reading