How to force a Java-process to perform GC?

While watching a Java-process with jconsole, I saw its heap-usage fluctuate between under 1Gb and above 3Gb. The same figures were output by the process itself -- we have GC-logging enabled.

On a whim I pressed the "Perform GC" button in jconsole, and the effect was quite dramatic -- the heap-usage fell under 1Gb and stayed there. It was still fluctuating, as might be expected, but never to the heights seen before.

the red arrow points at when I forced garbage-collection

This is Java-11 on Linux. Are there, perhaps, some JVM options to achieve more aggressive garbage-collection without a manual intervention? We have the CPU -- this app is mostly I/O bound anyway -- but we could use the 2Gb better than to store garbage...

Update, no, this is not a duplicate of the earlier question:

  • I'm not asking why here, I'm asking for practical tips on what to do.
  • The JVM is much newer -- 7 years, and G1 is now the default GC, unlike back then.

How to force a Java-process to perform GC?

It can trigger a full garbage collection by calling System.gc(). Or you can do this externally; e.g. with jconsole or (I think) jcmd.

Caveats:

  1. Depending on JVM's command line options a call to System.gc() may be ignored; i.e. it may do nothing.

  2. Calling System.gc() on a regular basis is a bad idea. It is bad for performance, because a full GC is expensive (compared to a new generation collection) and because running the GC when there isn't a lot of garbage does a lot of work for little benefit.

    See Why is it bad practice to call System.gc()?


The interesting issue is why triggering a GC had that (on the face of it) beneficial effect. There is not enough evidence to be sure, but I suspect that your JVM's heap had probably expanded to deal with a previous event where it has a very large working set. Those object were presumably garbage collected, but the JVM was left with an oversized heap.

The JVM will downsize the heap when it detects that it is too large. However that determination is only made following a full GC. Presumably, since what was happening before you hit the button was that the JVM was only doing new space collections ... because the fast majority of new objects were "dying young". That meant that the heap stayed large.

If the above hypothesis is correct, there is a related issue. What if the heap actually needs to be that large in the long term? What if you get another event where the GC needs a very large working set? Answer, it will need to grow the heap again. And that will entail at least one full GC and possibly more ... to get it to the required size.

What to do?

  • Some Java GCs allow you to tune the heap resizing parameters.
  • Some Java GCs allow you to run the GC periodically ... and make the periodic GC a full GC.
  • Some Java GCs are designed to be less inclined to "hog" memory.

I would recommend using those rather than calling System.gc() ... and hoping.

Or maybe the best thing to do would be to leave it alone.

Question: Imagine if you squeeze the JVM's heap usage and then use the RAM you reclaimed for other things. Then another batch comes on requiring a (temporarily) large heap again. What happens?

Answer: bad things! Your Java heap expands to accommodate the larger working set, the system runs short of RAM and starts stealing pages from other processes, the system thrashing, and the OOM killer starts killing process.


Probably the most up to date resource on GC tuning is:

  • HotSpot Virtual Machine Garbage Collection Tuning Guide for Java 17.