Running on Java 24-ea+25-3155 (Preview)
Home of The JavaSpecialists' Newsletter

317Explicit vs Diagnostic GC

Author: Dr Heinz M. KabutzDate: 2024-04-30Java Version: 17Category: Performance
 

Abstract: How many times have we seen programmers call System.gc() to "help the garbage collector"? Unfortunately far too often, with potentially terrible performance implications. In this newsletter we explore the explicit and the diagnostic GC and how we can force a GC, even if explicit GCs are disabled.

 

Welcome to the 317th edition of The Java(tm) Specialists' Newsletter. My target for 2024 was to write a newsletter every month, preferably before the last day. But then today the weather on Crete was slighly overcast, but comfortably warm, and my wife's godsister Stasa came to visit with her husband and brother. We decided to spend the afternoon trying out a new taverna overlooking Tersanas Beach. It was perfect, with delicious fresh fish, and other well-prepared Cretan dishes. And so, our afternoon went by, and now I'm rushing to get this newsletter ready for you. I hope you enjoy it :-)

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Explicit vs Diagnostic GC

A few weeks ago, Kirk Pepperdine and I decided to create a recording of a course he wrote on solving Java memory leaks. It's super easy - look at the generational count to find the culprit, then start tracing the roots of these objects to find where they are leaking from.

Talking of leaks, here is a small story that happened to us recently. We live in the middle of nowhere and our water meter is far from our house. I opened our last water bill, and had to rub my eyes. Instead of our usual bill of $100-$200 for 3 months, I was staring of a figure of over $1000. How could this be? We try and economize our water usage as much as possible. In July 2022, our water line ran dry. We discovered that whilst cutting back some bushes, a farmer must have sawn through our pipe. He had dutifully stopped the leak by bending over the pipe. Then this year, during our Ascension Day church service, I noticed that the joint we had put in was leaking. As soon as we could, we repaired the leak, not knowing how much water had flowed into the sea. Months later the bill arrived. Fortunately, this being Crete, the water department was very understanding and wrote off a large part of that horrendous bill. In the meanwhile they had moved the water meter much closer to our house. We still have the occasional leak, but hopefully not $1000 worth. The lesson on leaks is: even a small leak will, over time, cause damage. Rather fix leaks!

Back to memory leaks. Some companies restart their servers every 24 hours, in order to "solve" their memory leaks. However, we feel that it is so easy to fix, that it is irresponsible to just leave them in our codebase. As I said above - look for the generational count to find the culprit, follow those types of objects to their GC roots, fix the leak - we're done. In case this does not seem obvious - we have recorded the course and you can buy it here. The course has two exercises with a detailed walkthrough. Since we recorded it with a live audience of two dozen of our JCretan friends, you also get the benefit of their questions and Kirk's answers.

During the course, Kirk was explaining the difference between an explicit GC and a diagnostic GC. The explicit GC happens when we call System.gc() inside our code, whereas the diagnostic GC is invoked through tooling. We can turn off the explicit GC with -XX:+DisableExplicitGC, but we cannot turn off the diagnostic GC. Kirk also mentioned that these events are logged differently in the GC logs.

I do not know anyone who has looked at GC logs as closely as Kirk, but I was sure that the tooling would simply call System.gc() and that the JVM setting -XX:+DisableExplicitGC would turn off these calls. I thus wrote this little class to test it:

import java.io.*;
import java.lang.management.*;

public class DiagnosticVsExplicitGC {
    public static void main(String... args) throws IOException {
        // turn on verbose GC
        ManagementFactory.getMemoryMXBean().setVerbose(true);

        // create some objects
        for (int i = 0; i < 1000; i++) {
            byte[] object = new byte[100_000];
        }

        // wait for input
        BufferedReader in = new BufferedReader(
                new InputStreamReader(System.in)
        );
        System.out.println("Press ENTER to invoke explicit GC");
        in.readLine();
        System.gc();
        System.out.println("Press ENTER to exit program");
        in.readLine();
    }
}

When we run this code, we immediately see that the GC is run due to the young space filling up. When we press ENTER, the GC log lists that System.gc() was called. Here is the output:

java DiagnosticVsExplicitGC.java
[0.297s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
    79M->18M(1568M) 1.344ms
[0.299s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause)
    66M->18M(1568M) 0.833ms
Press ENTER to invoke explicit GC

[6.613s][info][gc] GC(2) Pause Full (System.gc()) 23M->18M(224M) 16.657ms
Press ENTER to exit program

We can turn off the explicit GC with the flag -XX:+DisableExplicitGC, in which case the output changes to:

java -XX:+DisableExplicitGC DiagnosticVsExplicitGC.java
[0.223s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
    79M->18M(1568M) 1.431ms
[0.225s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause)
    66M->18M(1568M) 0.870ms
Press ENTER to invoke explicit GC

Press ENTER to exit program

We have seen systems where they regularly call System.gc() in order to "help the garbage collector". This is not necessary, and in most cases, is harmful. The explicit GC typically triggers a stop-the-world full GC event, which is one of the worst case scenarios for a JVM to experience.

We can configure how our garbage collectors respond to an explicit GC. Instead of doing a full GC, we can trigger the start of a concurrent GC with -XX:+ExplicitGCInvokesConcurrent. This option works for G1, and is on by default for Shenandoah.

Getting back to the difference between the explicit GC and the diagnostic GC, let us have a look at what causes which type of GC. We already established that System.gc() triggers the explicit GC, which may be turned off, or which may start a concurrent GC instead a full GC. In addition, when we click on "Perform GC" in the Memory tab of JConsole, or the Monitor tab of VisualVM, the explicit GC is invoked (which may be turned off). However, when we call GC.run with the jcmd tool, then the "Diagnostic Command" is invoked. This ignores the -XX:+DisableExplicitGC flag, but honours the -XX:+ExplicitGCInvokesConcurrent flag. JDK Mission Control's MBean Browser gc() operation on the Memory bean is an explicit GC, with the diagnostic command of GC.run again, like jcmd, is a diagnostic GC.

Another curiosity is that the jcmd diagnostic tool has changes over the years. In Java 7, in the GC logs, I could not spot a difference in the GC Cause between the explicit and diagnostic GC (enabled with -XX:+PrintGCCause). However, when invoking GC.run with jcmd, the GC was called. In Java 8, again the logs did not appear to show the difference, but jcmd prevented me from calling the GC.run diagnostic command when explicit GC was disabled. From Java 11 onward, we see a clear distinction in the GC logs between the explicit and diagnostic GC. In addition, jcmd allows us to issue the GC.run command even if we have disabled explicit GC.

Kirk pointed out to me that since the DiagnosticCommand is just an MBean, we can also call that through JConsole or VisualVM, by using the com.sun.management.DiagnosticCommand MBean. We can even do that programmatically, like so:

import javax.management.*;
import java.lang.management.*;

public class DiagnosticCommand {
    public static void gcRun() {
        try {
            var server = ManagementFactory.getPlatformMBeanServer();
            var name = new ObjectName(
                    "com.sun.management:type=DiagnosticCommand");
            server.invoke(
                    name,
                    "gcRun",
                    new Object[0], new String[0]);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
}

Both the explicit and the diagnostic GC call heap()->collect() in the C++ layer of the JVM. The diagnostic GC looks like this (diagnosticCommand.cpp)

Universe::heap()->collect(GCCause::_dcmd_gc_run);

The explicit GC is invoked with System.gc(), and is implemented like this (jvm.cpp)

if (!DisableExplicitGC) {
  EventSystemGC event;
  event.set_invokedConcurrent(ExplicitGCInvokesConcurrent);
  Universe::heap()->collect(GCCause::_java_lang_system_gc);
  event.commit();
}

A small tip. When using tools like jcmd, instead of using the process id (pid), rather pass in the name of the class. In other words, instead of jcmd 34512, it is often more convenient to call jcmd DiagnosticVsExplicitGC. The only catch is if we have several processes with the same name, then it will send our diagnostic command to all of them. We might not want this.

Let's stop the leaks and save our planet.

Kind regards

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '24 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...