There are many posts about the Java garbage collector, and many of them are old: PermGen, CMS, -XX:+PrintGCDetails… This guide sums up what is true in Java 25, in a few lines per topic. Each part links to a longer post, where every fact is checked in the official documentation or on a real JVM.

I update this guide when the series moves on, or when a new Java version changes the rules.

JVM memory

The JVM keeps its data in several areas:

  • the stack: one per thread, with one frame per method call (parameters, local variables). It is freed by itself, without the GC. Too many nested calls give a StackOverflowError;
  • the heap: the objects created with new, shared by all threads. This is the area of the garbage collector. Its maximum size is set with -Xmx;
  • the Metaspace: the description of the classes, in native memory, outside the heap. It replaced PermGen in Java 8.

The JVM also uses native memory for thread stacks, the code compiled by the JIT, direct buffers and its own internal data. -Xmx only limits the heap: the Java process always uses more.

→ JVM memory: heap, stack and Metaspace

Generations and cycles

Most objects die young. So the JVM keeps recent objects in the young generation (eden and two survivor spaces) and long-lived ones in the old generation:

How an object moves in the heap: it is created in eden, moves from one survivor space to the other at each minor collection, then it is promoted to the old generationHeapYoungnew objectsEdenobjects are created hereobjectSurvivor 0the two survivor spacesSurvivor 1take turns; one is emptyOldlong-lived objectsobject1231Minor collection: the live object is copied from eden to a survivor space.2At each minor collection, it moves to the other survivor space; its age goes up.3After enough copies (MaxTenuringThreshold, 15 by default): promoted to old.

An object is born in eden, moves from one survivor space to the other at each GC, and is then promoted to the old generation after a number of copies (15 at most by default).

In Oracle’s guide, a minor collection cleans only the young generation, and a major collection cleans the whole heap. G1, the default GC, does not use these words in its logs: it talks about Young, Mixed and Full pauses. And since Java 9, it splits the heap into regions of the same size, instead of two big blocks.

→ GC generations and cycles: young, old, minor, major, full

The garbage collectors

Java 25 offers several GCs, each one for a need:

  • G1, the default GC since Java 9: when response time matters more than throughput;
  • ZGC and Shenandoah: very short pauses, for latency;
  • Parallel: throughput first;
  • Serial: a single thread, for small machines;
  • Epsilon: never frees anything, for tests.

CMS is gone since Java 14. Also watch the automatic choice: on a machine with a single processor or less than 1792 MB of memory, the JVM picks Serial instead of G1. So a small container can run with a different GC than the developer’s machine. To check it: -Xlog:gc writes the GC in use on its first line (Using G1).

→ Java garbage collectors in 2026: which one is running yours?

Useful options

A few options are enough in most cases:

  • -Xms and -Xmx: the initial and maximum heap size, often the same value on a server;
  • -Xlog:gc*:file=gc.log: GC logs to a file, with rotation built in;
  • -XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath: a heap dump when the heap is full;
  • -XX:MaxRAMPercentage: the maximum heap as a percentage of memory (25% by default), useful in a container.

Many old options no longer work. Some are only deprecated (-XX:+PrintGCDetails, -Xloggc), but others stop the JVM at startup in Java 17, 21 and 25: -XX:MaxPermSize, -XX:+UseConcMarkSweepGC, -XX:+PrintGCDateStamps… To test a configuration, just run java <options> -version.

→ JVM options that matter, and the ones that no longer work

Common myths

  • System.gc() is only a request. But with G1, Parallel and Serial, it usually starts a full GC, which is expensive.
  • finalize() is deprecated for removal: use try-with-resources or Cleaner instead.
  • More heap often helps, as long as it fits in the memory of the machine or the container.
  • Tuning the GC is not the first thing to do: G1 with its default settings, then the logs, then the application.
  • The GC does not prevent memory leaks: an object that is still referenced is never freed.

→ Common myths about the Java garbage collector

FAQ

What is the difference between the heap and the stack?

The stack belongs to one thread: it holds the local variables and is emptied when the method ends. The heap is shared by all threads: it holds the objects, and the GC cleans it. A local variable of an object type is on the stack, but the object itself is in the heap.

What is the default GC in Java 25?

G1, on a “server-class” machine: at least two processors and at least 1792 MB of memory. Otherwise, the JVM picks Serial.

Can I force the GC?

No. System.gc() is a request, with no guarantee. With G1, Parallel and Serial, it usually gives a full GC. With -XX:+DisableExplicitGC, the JVM ignores these calls.

Why does my application freeze for a moment?

GC pauses are one possible cause, not the only one. To find out, look at the GC logs (-Xlog:gc*): each pause is written there with its duration. With G1, a Pause Full that does not come from System.gc() is a sign that the heap is too full, and the docs warn that it can be very slow. Full GCs that keep coming back point to a heap that is too small or a memory leak.

Which GC for a Spring Boot application?

Spring Boot does not choose the GC: the JVM does, with the same rules as for any Java application. A Spring Boot 3.5 application on Java 25 starts with G1, and with Serial if the JVM sees only one processor. Start with G1 and its default settings, and check the GC in use with -Xlog:gc.

What comes next in the series

This guide covers the basics. The rest of the series will go further:

These posts will be added to this guide as they come out.