The JVM’s garbage collector is more of a traffic cop than a janitor, actively orchestrating memory allocation and deallocation to prevent collisions, not just cleaning up afterward.
Let’s watch a real-time example. Imagine a web server handling requests. Each request might create a few short-lived objects (like temporary strings or request data). The JVM’s garbage collector needs to efficiently reclaim this memory without pausing the server for too long.
Here’s a simplified view of the heap:
+-------------------------------------------------+
| Young Generation (Eden, S0, S1) |
| +-----------------+ +-----------------+ |
| | Eden | | Survivor Space 0| |
| +-----------------+ +-----------------+ |
| +-----------------+ |
| | Survivor Space 1| |
| +-----------------+ |
+-------------------------------------------------+
| Old Generation (Tenured) |
| +-----------------------------------------+ |
| | Objects that survived Young GC | |
| +-----------------------------------------+ |
+-------------------------------------------------+
| Metaspace (or PermGen in older JVMs) |
| +-----------------------------------------+ |
| | Class metadata, method data | |
| +-----------------------------------------+ |
+-------------------------------------------------+
When new objects are created, they land in the Eden space. When Eden fills up, a Minor Garbage Collection (often using the Young GC algorithm like ParNew or the G1 Young GC phase) happens. It copies live objects from Eden to one of the Survivor Spaces (S0 or S1). Objects that have been copied multiple times (meaning they’ve survived several Minor GCs) are eventually promoted to the Old Generation (Tenured space). When the Old Generation fills up, a Major Garbage Collection (or Full GC) occurs, which is much more disruptive.
The primary goal of tuning is to minimize the frequency and duration of these GC pauses, especially Full GCs.
Key Levers:
- Heap Size (
-Xms,-Xmx):-Xms: Initial heap size.-Xmx: Maximum heap size.- Why it matters: Too small a heap leads to frequent GCs. Too large a heap can lead to longer GC pauses (especially Full GCs) and wasted memory. A common starting point is setting
XmsandXmxto the same value to prevent resizing overhead. For a server, you might start with-Xms2g -Xmx2g.
- Garbage Collector Algorithm (
-XX:+Use...GC):- Serial GC (
-XX:+UseSerialGC): Single-threaded, pauses all application threads. Good for very small heaps and single-core machines. - Parallel GC (
-XX:+UseParallelGC): Throughput collector. Uses multiple threads for Young GC, but still pauses for Old GC. Good for batch processing where throughput is key and pauses are tolerable. - CMS (
-XX:+UseConcMarkSweepGC): Concurrent Mark Sweep. Attempts to do most of the Old Generation GC work concurrently with the application, reducing pause times. Deprecated and removed in later JVMs. - G1 GC (
-XX:+UseG1GC): Garbage-First. Divides the heap into regions. Aims for predictable pause times by collecting the "garbage-first" regions. Default in modern JVMs (Java 9+). - ZGC (
-XX:+UseZGC) & Shenandoah (-XX:+UseShenandoahGC): Ultra-low-pause collectors. Designed for very large heaps and extremely short pause times. - Why it matters: Different collectors have different trade-offs between throughput, latency, and memory usage. G1 is a good default for many applications. If you need sub-millisecond pauses, explore ZGC or Shenandoah.
- Serial GC (
- Thread Stack Size (
-Xss):- Why it matters: Each thread gets its own stack. A large stack size consumes more memory per thread. If you have thousands of threads, this can quickly exhaust memory. The default is typically 1MB. For applications with many threads, reducing this to, say,
-Xss256kcan save significant memory.
- Why it matters: Each thread gets its own stack. A large stack size consumes more memory per thread. If you have thousands of threads, this can quickly exhaust memory. The default is typically 1MB. For applications with many threads, reducing this to, say,
- Metaspace Size (
-XX:MaxMetaspaceSize):- Why it matters: Stores class metadata. If this fills up, you can get
OutOfMemoryError: Metaspace. While not directly GC of application objects, it’s a critical memory area. Setting a reasonable limit, like-XX:MaxMetaspaceSize=256m, prevents runaway class loading from crashing the JVM.
- Why it matters: Stores class metadata. If this fills up, you can get
When you’re tuning, you’ll often look at GC logs. Enable them with -Xlog:gc* (for modern JVMs) or -XX:+PrintGCDetails -XX:+PrintGCTimeStamps (for older ones). Look for frequent Full GC events, long pause times, and high GC activity relative to application execution.
The ultimate goal of tuning is not to eliminate GC, but to make it a silent, efficient background process that doesn’t impact user experience.
The next hurdle you’ll likely encounter is diagnosing specific memory leaks, which GC tuning alone cannot fix.