You still read a lot of advice about the garbage collector. Some of it was wrong from the start, and some of it is not true anymore.
Here are five common myths, and what the official documentation and a real JVM say in Java 25.
“System.gc() forces a GC”
No: it is a request, not an order. The javadoc says that System.gc()
“suggests” that the JVM frees memory, with no guarantee about how much
is freed, or even that the work is done at all.
In practice, on Java 25, the request is often followed, and it is expensive:
| GC | What System.gc() does |
|---|---|
| G1 (default), Parallel, Serial | A full GC: Pause Full (System.gc()) |
| ZGC | A major collection: Major Collection (System.gc()) |
G1 with -XX:+ExplicitGCInvokesConcurrent | A young pause, then a concurrent cycle |
With -XX:+DisableExplicitGC | Nothing: the call is ignored |
With -XX:+DisableExplicitGC, the JVM ignores calls to System.gc(),
but it still runs a GC when it needs one.
“finalize() is the place to free resources”
No. finalize() is deprecated since Java 9, and deprecated for removal
since Java 18 (JEP 421). The GC can call
it very late, with no guaranteed delay. It runs on any thread, in any
order. And if the finalizer thread cannot keep up, the objects that wait
for it can fill the heap, up to an OutOfMemoryError.
Use instead:
- a
close()method, withAutoCloseableand try-with-resources: the close is guaranteed, even when an exception is thrown; Cleaner(Java 9), to run a cleanup action when the object can no longer be reached.
To check that an application no longer depends on finalizers, you can
start it with --finalization=disabled (since Java 18).
“More heap is always better”
Often true, but not always. Oracle’s guide says to give the JVM as much memory as possible, unless you have problems with pauses. The default size is often too small.
But there are limits:
- the maximum heap must stay below the memory of the machine, or the system starts paging;
- the JVM also uses memory outside the heap (Metaspace, thread stacks…): in a container, all of it must fit in the limit;
- with G1, a bigger heap usually makes the marking phase longer.
The options to set the heap size are in the post about JVM options.
“Tuning the GC is the first thing to do”
No. Oracle’s guide says to start the application first and let the JVM
choose the GC. For G1, its general advice is to keep the default
settings, with at most a different pause time goal and a maximum heap
size (-Xmx).
Before you change an option, look at what happens: the GC logs are a
good start (-Xlog:gc*). And the guide reminds us: a change in the
application can work better than tuning the JVM.
“The GC prevents memory leaks”
No. The GC frees the objects that nothing references anymore. But for
Oracle, a memory leak in Java is exactly an application that keeps
references to objects or classes without meaning to: a cache that only
grows, a list that is never cleared… The GC cannot free them. The heap
fills up, or the Metaspace for classes, up to an OutOfMemoryError.
There are also native memory leaks, outside the heap. For the ones inside the JVM itself, the Native Memory Tracking (NMT) tool helps to find them.
What has changed
| What you still read | What is true today | Since |
|---|---|---|
Free resources in finalize() | finalize() is deprecated for removal; use try-with-resources or Cleaner | Java 9 (deprecated), Java 18 (for removal) |
| A class leak fills the PermGen | PermGen is gone: the Metaspace fills up | Java 8 |
| Tune the CMS options | CMS is gone; G1 is the default GC, and its default settings are the advice | Java 9 (G1 default), Java 14 (CMS removed) |
Sources: the javadoc of System.gc()
and Object.finalize(),
JEP 421 (finalization),
the documentation of the java command,
Oracle’s garbage collection guide
and the troubleshooting guide about memory leaks
(JDK 25).
Summary
System.gc() is an expensive request, finalize() is something to
forget, a bigger heap helps as long as it fits in memory, you measure
before you tune, and the GC does not protect you from references you
forgot.