Your application may be running with a different GC than you think.
In Java 25, G1 is the default garbage collector. But the JVM offers others, each one for a different need. And on a small machine, it picks another one on its own.
The GCs available in Java 25
| GC | What Oracle’s guide says | Option |
|---|---|---|
| Serial | One thread does all the GC work. For a small data set (up to about 100 MB) or a single processor | -XX:+UseSerialGC |
| Parallel | The “throughput collector”: like Serial, but with several threads. When raw performance comes first and pauses of a second or more are fine | -XX:+UseParallelGC |
| G1 | A “mostly concurrent” collector: it does part of its work while the application runs. When response time matters more than throughput | -XX:+UseG1GC |
| ZGC | Pauses under a millisecond, at the cost of some throughput. When low latency is the priority | -XX:+UseZGC |
| Shenandoah | Another low-pause GC. Oracle’s guide does not cover it; it works on Temurin 25 | -XX:+UseShenandoahGC |
And a special one: Epsilon. It allocates memory but never frees it.
When the heap is full, the JVM stops. It is meant for tests (measuring
an application without any GC, checking that a test does not create too
many objects) or very short jobs. In Java 25, it is still experimental:
you need -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC.
The default GC: G1… except on a small machine
The JVM picks G1 on a “server-class” machine: at least two processors and at least 1792 MB of physical memory. Otherwise, it picks Serial.
On a developer machine, it is almost always G1. But in a small container limited to one CPU, the JVM silently switches to Serial. So your application can behave differently on your laptop and in production.
Oracle’s advice: unless you have strict pause-time requirements, run your application first and let the JVM choose.
How to check which GC is running
At startup, with -Xlog:gc, the JVM tells you on the first line:
[0.015s][info][gc] Using G1
Without starting your application, -XX:+PrintFlagsFinal shows what
the JVM picks on this machine (ergonomic means “chosen by the JVM”):
bool UseG1GC = true {product} {ergonomic}
And for an application that is already running, jcmd <pid> VM.flags
lists its options, for example -XX:+UseG1GC.
What has changed
| What you still read online | What is true today | Since |
|---|---|---|
| Parallel is the default GC | G1 is the default GC | Java 9 |
| CMS is the low-pause GC | CMS is gone. On Java 17, 21 and 25, -XX:+UseConcMarkSweepGC even stops the JVM from starting | Deprecated in Java 9, removed in Java 14 |
| ZGC and Shenandoah are experimental | They are ready for production | Java 15 |
| ZGC has no generations | ZGC is generational, and that is the only mode since Java 24 | Java 21 to 24 |
| Shenandoah has no generations | Its generational mode is stable in Java 25, but it is not the default (-XX:ShenandoahGCMode=generational) | Java 25 |
Sources: Oracle’s garbage collection guide (JDK 25), JEP 248 (G1 by default), JEP 291 and JEP 363 (CMS), JEP 377 (ZGC), JEP 379 and JEP 521 (Shenandoah), JEP 318 (Epsilon).
Summary
G1 is a good place to start, and it is what the JVM picks on a normal machine. ZGC and Shenandoah aim for the shortest pauses, Parallel for throughput, Serial for small machines. Before you switch GCs, check which one is really running, especially in a container.