Garbage Collection (GC) is an automatic memory management mechanism in Java that
identifies and reclaims memory occupied by objects that are no longer in use. This
frees developers from manual memory management, reducing memory leaks and dangling
pointer bugs that plague languages like C and C++. Understanding GC algorithms is
essential for building high-performance Java applications.
The Java Virtual Machine (JVM) has evolved significantly since its inception,
introducing increasingly sophisticated garbage collectors to meet the demands of
modern applications. From the original Serial collector to today's ultra-low-latency
collectors like ZGC and Shenandoah, each generation has brought improvements in
throughput, pause times, and scalability.
This document explores the major garbage collection algorithms available in modern
Java: G1 (the default), ZGC, Shenandoah, and Epsilon. Each collector is designed
for specific use cases, and choosing the right one can dramatically impact
application performance.
Garbage collection automates the process of reclaiming memory from objects that
are no longer reachable by the application. The JVM divides the heap into regions
and uses various strategies to identify and collect garbage efficiently.
The JVM heap is traditionally divided into generations based on object lifetimes:
| Region | Description |
|---|---|
| Young Gen | Newly allocated objects; most objects die here (short-lived) |
| Eden Space | Initial allocation area within Young Generation |
| Survivor | Objects that survived one or more Young GC cycles |
| Old Gen | Long-lived objects promoted from Young Generation |
| Metaspace | Class metadata and method information (not in heap) |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β JVM Heap β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββ β
β β Young Generation β β Old Generation β β
β β βββββββββββββββββββββββββββββ β β β β
β β β Eden Space β β β Long-lived objects that β β
β β β (New allocations) β β β survived multiple GC cycles β β
β β βββββββββββββββββββββββββββββ β β β β
β β ββββββββββββββ ββββββββββββββ β β β β
β β β Survivor 0 β β Survivor 1 β β β β β
β β β (From) β β (To) β β β β β
β β ββββββββββββββ ββββββββββββββ β β β β
β βββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Modern collectors like ZGC and Shenandoah may use different heap layouts,
organizing memory into uniformly-sized regions rather than generations.
Objects go through a predictable lifecycle from allocation to collection:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Object Lifecycle β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββββββββ β
β β Object β β Object β β Object β β Object is β β
β β Created ββββββΊβ In Use ββββββΊβ Becomes ββββββΊβ Collected β β
β β in Eden β β (Reach- β β Unreach- β β (Memory freed) β β
β β β β able) β β able β β β β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββββββββ β
β β β
β β If survives GC β
β βΌ β
β ββββββββββββ ββββββββββββ β
β β Promoted β β Promoted β β
β β to ββββββΊβ to β β
β β Survivor β β Old Gen β β
β ββββββββββββ ββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Most garbage collectors perform these fundamental phases:
| Phase | Description |
|---|---|
| Marking | Identify all reachable (live) objects starting from GC roots |
| Sweeping | Identify unreachable objects as garbage |
| Compacting | Move live objects together to eliminate fragmentation |
| Copying | Copy live objects to a new region (alternative to compacting) |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GC Phases Overview β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. MARKING PHASE β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β GC Roots βββΊ Live Object βββΊ Live Object βββΊ Live Object β β
β β β β β β
β β βΌ βΌ β β
β β Live Object Live Object [Dead] [Dead] β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β 2. SWEEPING PHASE β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β [Live] [ Free ] [Live] [Live] [ Free ] [Live] β β
β β Dead objects reclaimed, memory fragmented β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β 3. COMPACTING PHASE β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β [Live][Live][Live][Live][ Free Space ] β β
β β Objects moved together, eliminating fragmentation β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
A stop-the-world (STW) pause occurs when the GC must pause all application
threads to safely perform certain operations. Reducing these pauses is a primary
goal of modern collectors.
| Pause Type | Description |
|---|---|
| Minor GC | Collection of Young Generation; typically short |
| Major GC | Collection of Old Generation; often longer |
| Full GC | Collection of entire heap; longest pause |
| Concurrent GC | GC work done while application runs (no pause) |
Modern collectors like ZGC and Shenandoah perform most work concurrently,
achieving sub-millisecond pause times regardless of heap size.
Understanding the difference between concurrent and parallel collection is crucial:
| Term | Description |
|---|---|
| Parallel | Multiple GC threads work together during STW pause |
| Concurrent | GC threads work while application threads continue running |
| Incremental | GC work is divided into smaller chunks across multiple pauses |
The Garbage First (G1) collector is the default garbage collector in Java 9+.
It is designed to provide high throughput with predictable pause times, making
it suitable for most applications. G1 balances the needs of large heaps with
the requirement for reasonable latency.
G1 divides the heap into equally-sized regions (typically 1-32 MB each) rather
than contiguous generations. Each region can be Eden, Survivor, Old, or Humongous
(for large objects).
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β G1 Region-Based Heap β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ β
β β E β β E β β S β β O β β O β β O β β E β β H β β
β βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ β
β βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ β
β β O β β E β β O β β F β β F β β O β β S β β H β β
β βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ β
β βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ β
β β O β β F β β O β β O β β E β β O β β F β β O β β
β βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ β
β β
β Legend: E = Eden, S = Survivor, O = Old, H = Humongous, F = Free β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
G1 operates in several phases to manage memory efficiently:
| Phase | Description |
|---|---|
| Young GC | Collects Eden and Survivor regions |
| Concurrent Marking | Marks live objects while application runs |
| Mixed GC | Collects Young regions plus selected Old regions |
| Full GC | Fallback collection of entire heap (avoided if ok) |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β G1 GC Cycle β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β Young GC ββββββΊβ Concurrent Marking ββββββΊβ Mixed GC β β
β β (STW) β β (Concurrent) β β (STW) β β
β βββββββββββββββ βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β (Cycle repeats) β
β β
β Concurrent Marking Phases: β
β 1. Initial Mark (STW) - Mark objects directly reachable from GC roots β
β 2. Root Region Scan - Scan survivor regions for references to old gen β
β 3. Concurrent Mark - Mark live objects throughout the heap β
β 4. Remark (STW) - Complete marking of remaining live objects β
β 5. Cleanup (STW/Conc) - Identify completely empty regions to reclaim β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
G1 provides several key benefits:
- Predictable pause times: Configurable pause time targets
- Region-based collection: Only collects regions with most garbage first
- Compaction: Reduces fragmentation during evacuation
- Scalability: Handles heaps from gigabytes to terabytes
- Adaptive: Automatically adjusts to meet pause time goals
Common JVM flags for configuring G1:
# Enable G1 (default in Java 9+)
-XX:+UseG1GC
# Set maximum pause time target (milliseconds)
-XX:MaxGCPauseMillis=200
# Set heap region size (1-32 MB, must be power of 2)
-XX:G1HeapRegionSize=16m
# Set percentage of heap for Old Generation threshold
-XX:InitiatingHeapOccupancyPercent=45
# Number of parallel GC threads
-XX:ParallelGCThreads=8
# Number of concurrent marking threads
-XX:ConcGCThreads=4
# Reserve memory for promotion failures
-XX:G1ReservePercent=10
# Enable string deduplication
-XX:+UseStringDeduplicationExample complete configuration for a web server:
java -Xms4g -Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=100 \
-XX:G1HeapRegionSize=8m \
-XX:InitiatingHeapOccupancyPercent=35 \
-XX:+UseStringDeduplication \
-jar application.jarYou can observe G1 behavior by enabling GC logging:
void main() {
// Allocate objects to trigger GC
var list = new ArrayList<byte[]>();
for (int i = 0; i < 100; i++) {
// Allocate 1 MB blocks
list.add(new byte[1024 * 1024]);
if (i % 10 == 0) {
// Release some references to create garbage
list.subList(0, Math.min(5, list.size())).clear();
}
}
// Force garbage collection
System.gc();
var runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();
IO.println("Used memory: " + usedMemory / (1024 * 1024) + " MB");
IO.println("Max memory: " + maxMemory / (1024 * 1024) + " MB");
}Run with GC logging enabled:
java --enable-preview --source 25 \
-XX:+UseG1GC \
-Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=10m \
GCDemo.javaThis produces detailed logs showing Young GC, concurrent marking phases, and
mixed GC events with timing information.
ZGC is an ultra-low-latency garbage collector designed to keep pause times under
1 millisecond regardless of heap size. It can handle heaps ranging from megabytes
to multi-terabytes while maintaining consistent sub-millisecond pauses.
ZGC was designed with specific goals in mind:
| Goal | Description |
|---|---|
| Ultra-low latency | Pause times < 1 ms regardless of heap size |
| Scalability | Support heaps from 8 MB to 16 TB |
| Concurrent | Almost all work done concurrently with application |
| No tuning required | Minimal configuration needed |
ZGC uses colored pointers to store metadata directly in object references.
This innovative approach allows ZGC to determine object state without accessing
the object itself.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ZGC Colored Pointer (64-bit) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Bit Layout (Linux/x64): β
β ββββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬βββββββββββββββββββββββββββββββββββ β
β β Unused β Final- β Remap β Mark 1 β Object Address β β
β β(16 bit)β izable β β Mark 0 β (42 bits) β β
β β β(1 bit) β(1 bit) β(2 bits)β β β
β ββββββββββ΄βββββββββ΄βββββββββ΄βββββββββ΄βββββββββββββββββββββββββββββββββββ β
β β
β Metadata bits (4 bits total): β
β - Finalizable: Object has a finalizer β
β - Remapped: Object has been relocated β
β - Marked: Object is reachable (two mark bits for alternating cycles) β
β β
β The 42-bit address supports up to 4 TB of heap address space β
β (16 TB with pointer compression disabled) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ZGC uses load barriers instead of write barriers. When an object reference
is loaded from the heap, the barrier checks if any action is needed (such as
updating a relocated pointer).
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ZGC Load Barrier β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Application Code: Load Barrier Check: β
β βββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββ β
β β Object ref = β β if (pointer needs remapping) { β β
β β obj.field β βββββββΊβ update pointer to new location β β
β βββββββββββββββββ β } β β
β β return corrected pointer β β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β
β Benefits: β
β - Application always sees up-to-date references β
β - Relocation can happen concurrently β
β - No stop-the-world pause needed for compaction β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ZGC operates in several concurrent phases with minimal STW pauses:
| Phase | Type | Description |
|---|---|---|
| Pause Mark Start | STW (<1ms) | Start marking, scan thread stacks |
| Concurrent Mark | Concurrent | Traverse object graph, mark live objs |
| Pause Mark End | STW (<1ms) | Complete marking |
| Concurrent Process | Concurrent | Process weak references |
| Concurrent Reset | Concurrent | Reset metadata for next cycle |
| Concurrent Relocate | Concurrent | Move objects, update references |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ZGC Cycle β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β STW (< 1ms) β Concurrent Work (Application Running) β STW β
β βββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββ βββββββ
β β Mark ββββΌββΊβ Concurrent Marking βββΊ Concurrent Relocation βββΌβ€Endββ
β β Start β β β β βββββββ
β βββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β
β Application β Application runs with load barriers β App β
β paused β checking/updating references as needed β run β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ZGC provides exceptional benefits for latency-sensitive applications:
- Sub-millisecond pauses: Typically < 0.5 ms, regardless of heap size
- Scalable: Supports heaps from 8 MB to 16 TB
- Concurrent compaction: No fragmentation without long pauses
- NUMA-aware: Optimized for multi-socket systems
- Self-tuning: Minimal configuration required
Common JVM flags for configuring ZGC:
# Enable ZGC
-XX:+UseZGC
# Enable generational ZGC (Java 21+, default in Java 23+)
-XX:+UseZGC -XX:+ZGenerational
# Set maximum heap size (ZGC works best with larger heaps)
-Xmx16g
# Set concurrent GC threads (default is 25% of CPU cores)
-XX:ConcGCThreads=4
# Soft max heap size (ZGC tries to stay below this)
-XX:SoftMaxHeapSize=8g
# Uncommit unused memory (returns memory to OS)
-XX:+ZUncommit
-XX:ZUncommitDelay=300
# Enable large pages for better performance
-XX:+UseLargePagesExample configuration for a low-latency trading application:
java -Xms32g -Xmx32g \
-XX:+UseZGC \
-XX:+ZGenerational \
-XX:SoftMaxHeapSize=28g \
-XX:+UseLargePages \
-XX:+AlwaysPreTouch \
-jar trading-app.jarJava 21 introduced Generational ZGC which separates young and old generations
while maintaining the sub-millisecond pause time guarantee:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Generational ZGC (Java 21+) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Benefits of Generational ZGC: β
β - Faster collection of short-lived objects β
β - Reduced memory overhead β
β - Better throughput for allocation-heavy workloads β
β - Maintains sub-millisecond pause times β
β β
β βββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββ β
β β Young Generation β β Old Generation β β
β β (Collected frequently) β β (Collected less often) β β
β β β β β β
β β Short-lived objects β β Long-lived objects promoted β β
β β Most garbage here β β from young generation β β
β βββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Shenandoah is a low-pause-time garbage collector that performs concurrent
compaction. It was developed by Red Hat and is included in OpenJDK. Like ZGC,
Shenandoah aims for pause times independent of heap size.
Shenandoah's key innovation is concurrent compactionβthe ability to compact
the heap while the application continues running. This is achieved through:
| Feature | Description |
|---|---|
| Brooks Pointers | Forwarding pointers in object headers |
| Read Barriers | Check if object has been relocated |
| Write Barriers | Ensure concurrent updates are safe |
| Self-healing | References automatically update during access |
Each object in Shenandoah contains a forwarding pointer that points to
itself initially and is updated to point to the new location during relocation:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Brooks Forwarding Pointer β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Before Relocation: β
β βββββββββββββββββββββββββββββββββββ β
β β βββββββββββββββββββββββββββ β β
β β β Forwarding Ptr βββββββ β β Points to itself β
β β βββββββββββββββββββββββββ β β β
β β β Object Header β β β β
β β βββββββββββββββββββββββββββ€ β β
β β β Object Data β β β
β β βββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββ β
β β
β After Relocation: β
β ββββββββββββββββββββββββββββ βββββββββββββββββββββββββββ β
β β Old Location β β New Location β β
β β ββββββββββββββββββββββ β β ββββββββββββββββββββββ β β
β β β Fwd Ptr βββββββββββββββββββββββΊβ Fwd Ptr βββββββ β β β
β β ββββββββββββββββββββββ€ β β ββββββββββββββββ β β β
β β β (Stale data) β β β β Object Header β β β
β β ββββββββββββββββββββββ β β ββββββββββββββββββββββ€ β β
β ββββββββββββββββββββββββββββ β β Object Data β β β
β β ββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Shenandoah operates in several phases:
| Phase | Type | Description |
|---|---|---|
| Init Mark | STW (<1ms) | Scan GC roots, prepare for marking |
| Concurrent Marking | Concurrent | Traverse object graph |
| Final Mark | STW (<1ms) | Complete marking, prepare for evac |
| Concurrent Cleanup | Concurrent | Reclaim regions with no live objects |
| Concurrent Evacuation | Concurrent | Copy live objects to new regions |
| Init Update Refs | STW (<1ms) | Prepare for reference updating |
| Concurrent Update Refs | Concurrent | Update all references to new locations |
| Final Update Refs | STW (<1ms) | Complete reference updating |
| Concurrent Cleanup | Concurrent | Reclaim evacuated regions |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Shenandoah GC Cycle β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββ Concurrent βββββββ Concurrent βββββββ Concurrent βββββββ β
β βInit β Marking βFinalβ Evacuation βInit β Update Refs βFinalβ β
β βMark ββββββββββββββΊβMark ββββββββββββββΊβUR ββββββββββββββΊβ UR β β
β β(STW)β β(STW)β β(STW)β β(STW)β β
β βββββββ βββββββ βββββββ βββββββ β
β <1ms <1ms <1ms <1ms β
β β
β Concurrent phases run alongside application threads β
β STW pauses are very brief - typically under 1 millisecond β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Shenandoah provides significant benefits:
- Consistent low pauses: Sub-millisecond pauses regardless of heap size
- Concurrent compaction: Eliminates fragmentation without STW pauses
- Wide availability: Available in most OpenJDK distributions
- Proven in production: Used in many enterprise applications
- Memory efficient: Reasonable memory overhead
Common JVM flags for configuring Shenandoah:
# Enable Shenandoah GC
-XX:+UseShenandoahGC
# Set heuristics mode (adaptive, static, compact, aggressive)
-XX:ShenandoahGCHeuristics=adaptive
# Percentage of heap to allocate before triggering GC
-XX:ShenandoahAllocationThreshold=10
# Free threshold to trigger concurrent GC
-XX:ShenandoahFreeThreshold=10
# Number of parallel GC threads
-XX:ParallelGCThreads=8
# Number of concurrent GC threads
-XX:ConcGCThreads=4
# Enable uncommit for returning memory to OS
-XX:ShenandoahUncommitDelay=1000
-XX:ShenandoahGuaranteedGCInterval=30000Example configuration for a microservices application:
java -Xms2g -Xmx2g \
-XX:+UseShenandoahGC \
-XX:ShenandoahGCHeuristics=compact \
-XX:+AlwaysPreTouch \
-jar microservice.jarShenandoah supports different heuristics modes for different workloads:
| Heuristic | Description |
|---|---|
| adaptive | Default; balances throughput and pause times |
| static | Triggers GC at fixed heap occupancy thresholds |
| compact | Aggressive compaction for fragmentation-prone workloads |
| aggressive | Continuous GC for testing and debugging |
| passive | Never initiates GC cycles (for testing only) |
Epsilon is a no-op garbage collector that handles memory allocation but
never reclaims any garbage. It is designed for specific use cases where GC
overhead must be eliminated or measured.
Epsilon is intentionally minimal:
| Feature | Description |
|---|---|
| Memory allocation | Handles object allocation as normal |
| No garbage collection | Never performs any collection |
| OutOfMemoryError | Application crashes when heap is exhausted |
| Zero GC overhead | No GC threads, barriers, or pauses |
Epsilon is useful for specific scenarios:
| Use Case | Description |
|---|---|
| Performance testing | Measure application without GC interference |
| Memory pressure testing | Find how much memory application truly needs |
| Short-lived applications | Jobs that complete before filling heap |
| Latency-sensitive testing | Establish baseline latency without GC pauses |
| GC algorithm comparison | Compare overhead of different collectors |
| Memory leak detection | Application quickly fails if leaking memory |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Epsilon GC Behavior β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Heap Usage Over Time: β
β β
β Memory β ββββ OutOfMemoryError β
β β² β ββββββ β
β β β βββββββ β
β β β βββββββ β
β MaxβββββΌβββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ β
β β β βββββββ β
β β β βββββββ β
β β β βββββββ β
β β β ββββββ β
β β βββ β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊ Time β
β Start Crash β
β β
β With Epsilon: Memory only increases, never reclaimed β
β Application must complete before heap exhaustion β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
JVM flags for Epsilon:
# Enable Epsilon GC (must explicitly unlock experimental features in older JDKs)
-XX:+UseEpsilonGC
# For older Java versions (before Epsilon became stable)
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
# Set heap size appropriately for your workload
-Xms4g -Xmx4g
# Optional: Exit on OutOfMemoryError for clean shutdown
-XX:+ExitOnOutOfMemoryError
# Optional: Heap dump on OOM for analysis
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dumpsExample configuration for performance baseline testing:
java -Xms8g -Xmx8g \
-XX:+UseEpsilonGC \
-XX:+ExitOnOutOfMemoryError \
-jar benchmark-app.jarThis example shows how Epsilon handles memory:
void main() {
var runtime = Runtime.getRuntime();
var list = new ArrayList<byte[]>();
IO.println("Starting Epsilon GC demonstration");
IO.println("Max heap: " + runtime.maxMemory() / (1024 * 1024) + " MB");
try {
for (int i = 0; i < 1000; i++) {
// Allocate 1 MB blocks
list.add(new byte[1024 * 1024]);
if (i % 50 == 0) {
long used = runtime.totalMemory() - runtime.freeMemory();
IO.println("Allocated " + (i + 1) + " MB, " +
"Used: " + used / (1024 * 1024) + " MB");
}
}
} catch (OutOfMemoryError e) {
IO.println("OutOfMemoryError: Heap exhausted as expected with Epsilon");
}
}Run with Epsilon enabled:
java --enable-preview --source 25 \
-Xms256m -Xmx256m \
-XX:+UseEpsilonGC \
EpsilonDemo.javaThe application will crash with OutOfMemoryError when the heap is exhausted,
demonstrating that Epsilon never reclaims memory.
This section compares the four garbage collectors across various dimensions:
| Feature | G1 GC | ZGC | Shenandoah | Epsilon |
|---|---|---|---|---|
| Default (Java 9+) | Yes | No | No | No |
| Typical pause times | 10-200 ms | < 1 ms | < 10 ms | None |
| Max pause times | 500+ ms | < 1 ms | < 10 ms | None |
| Heap size support | GB to TB | MB to 16 TB | MB to TB | Any |
| Throughput | High | High | High | Maximum |
| Memory overhead | Low-Medium | Medium | Medium | None |
| Concurrent compact | Partial | Yes | Yes | N/A |
| Generational | Yes | Yes (Java 21+) | No | N/A |
| JDK availability | All | OpenJDK 11+ | OpenJDK 12+ | OpenJDK 11+ |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Typical Pause Time Comparison β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Pause Time (ms) β
β β
β 500 ββ€ β
β β βββββ G1 Full GC (worst case) β
β β βββββ β
β 200 ββ€ βββββ β
β β βββββ β
β β βββββ β
β 100 ββ€ βββββ G1 Mixed GC β
β β βββββ β
β β βββββ β
β 50 ββ€ βββββ β
β β βββββ β
β β βββββ βββ G1 Young GC β
β 10 ββ€ βββββ βββ βββ Shenandoah β
β β βββββ βββ βββ β
β 1 ββ€ βββββ βββ βββ ZGC βββ β
β β βββββ βββ βββ βββ β
β 0 ββΌβββββββββββββββββββββββββββββββββββββββββββΊ Epsilon (no pauses) β
β β G1 ZGC Shenandoah Epsilon β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Application Type | Recommended GC | Reason |
|---|---|---|
| General purpose | G1 | Good balance, well-tested |
| Web services (typical) | G1 | Predictable performance |
| Low-latency trading | ZGC | Sub-ms pauses critical |
| Real-time systems | ZGC/Shenandoah | Consistent low latency |
| Large heap (> 32 GB) | ZGC | Scales well with heap size |
| Microservices | G1/Shenandoah | Good startup, reasonable pause |
| Batch processing | G1 | High throughput, pauses okay |
| Performance testing | Epsilon | Zero GC interference |
| Short-lived processes | Epsilon | No GC needed |
| Collector | Overhead Source |
|---|---|
| G1 | Remembered sets, region metadata (~5-10% overhead) |
| ZGC | Colored pointers, multi-mapping (~10-15% overhead) |
| Shenandoah | Brooks pointers, barriers (~10-15% overhead) |
| Epsilon | None (but never frees memory) |
Selecting the appropriate garbage collector depends on your application's
requirements. Use this decision framework to guide your choice.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GC Selection Decision Tree β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β What are your primary requirements? β
β β
β ββββββββββββββ β
β β Start β β
β βββββββ¬βββββββ β
β β β
β βββββββββββββββββΌββββββββββββββββ β
β βΌ βΌ βΌ β
β ββββββββββββββ ββββββββββββββ ββββββββββββββ β
β β Ultra-low β β General β βPerformance β β
β β latency β β purpose β β testing β β
β β (< 1 ms) β β β β β β
β βββββββ¬βββββββ βββββββ¬βββββββ βββββββ¬βββββββ β
β β β β β
β βββββββ΄ββββββ β β β
β βΌ βΌ βΌ βΌ β
β ββββββββββ ββββββββββ ββββββββββ ββββββββββββββ β
β β ZGC β βShenan- β β G1 β β Epsilon β β
β β β β doah β β (def) β β β β
β ββββββββββ ββββββββββ ββββββββββ ββββββββββββββ β
β β
β Further considerations: β
β - ZGC: Best for heaps > 4GB, Java 17+ β
β - Shenandoah: Good alternative to ZGC, widely available β
β - G1: Best general choice, extensive tuning options β
β - Epsilon: Only for testing or short-lived processes β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Latency Requirement | Recommended GC | Configuration |
|---|---|---|
| Any pause acceptable | G1 | Default settings |
| < 200 ms 99th percentile | G1 | Tuned MaxGCPauseMillis |
| < 50 ms 99th percentile | G1/Shenandoah | Careful tuning |
| < 10 ms 99th percentile | ZGC/Shenandoah | Default is usually fine |
| < 1 ms 99th percentile | ZGC | Default settings |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Throughput vs Latency Trade-off β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Throughput β² β
β (high) β β
β β βββββββββββ β
β β β Epsilon β (no GC overhead, but OOM risk) β
β β βββββββββββ β
β β βββββββββββ β
β β β G1 β (high throughput, moderate pauses) β
β β βββββββββββ β
β β ββββββββββββββ β
β β β Shenandoah β (good throughput, low pauses) β
β β ββββββββββββββ β
β β βββββββββββ β
β β β ZGC β (good throughput, < 1ms pause) β
β β βββββββββββ β
β (low) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊβ
β Low High β
β Latency (pause times) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Heap Size | Recommended GC | Notes |
|---|---|---|
| < 256 MB | G1 or Serial | G1 overhead may be significant |
| 256 MB - 4 GB | G1 | Default, well-tuned |
| 4 GB - 32 GB | G1 or ZGC | Both work well |
| 32 GB - 256 GB | ZGC or Shenandoah | Low-latency collectors preferred |
| > 256 GB | ZGC | Designed for very large heaps |
Following these best practices helps ensure optimal GC performance and
application stability.
Use these tools to monitor GC behavior:
| Tool | Description |
|---|---|
| JDK Flight Recorder | Low-overhead profiling built into JVM |
| VisualVM | GUI for monitoring GC, heap, threads |
| GC Logs | Detailed GC event information |
| jstat | Command-line GC statistics |
| jcmd | Diagnostic commands for running JVM |
Always enable GC logging in production:
# Modern unified logging (Java 9+)
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=100m
# More detailed logging
-Xlog:gc+heap=debug:file=gc.log:time,uptime:filecount=5,filesize=50m
# Log to both file and console
-Xlog:gc*:file=gc.log:time -Xlog:gc:stdout:timeKey metrics to monitor in GC logs:
| Metric | Healthy Range | Warning Signs |
|---|---|---|
| GC pause time | < target | Exceeds MaxGCPauseMillis |
| GC frequency | Stable | Increasing over time |
| Heap after GC | < 70% of max | Consistently > 80% |
| Allocation rate | Stable | Sudden spikes |
| Promotion rate | Low relative to alloc | High promotion rate |
| Full GC frequency | Rare or never | Frequent Full GCs |
General tuning recommendations:
# Start with reasonable heap sizing
-Xms4g -Xmx4g # Equal min/max avoids resize pauses
# Pre-touch memory for consistent performance
-XX:+AlwaysPreTouch
# Use large pages if available (improves TLB efficiency)
-XX:+UseLargePages
# Set appropriate thread counts
-XX:ParallelGCThreads=8 # Usually = CPU cores
-XX:ConcGCThreads=2 # Usually = ParallelGCThreads/4
# Enable string deduplication for string-heavy apps
-XX:+UseStringDeduplication # G1 only| Pitfall | Problem | Solution |
|---|---|---|
| Over-tuning | Breaks self-tuning | Start with defaults |
| Ignoring memory leaks | GC cannot fix leaks | Profile and fix app code |
| Undersized heap | Excessive GC overhead | Increase heap size |
| Oversized heap | Long GC pauses (G1) | Right-size or use ZGC |
| Explicit System.gc() | Triggers unexpected Full GC | Remove or disable |
| Finalizers | Delays object reclamation | Use try-with-resources |
| Ignoring GC logs | Problems go unnoticed | Monitor in production |
When GC cannot keep up, suspect a memory leak:
void main() {
// Common memory leak patterns to avoid:
// 1. Static collections that grow unboundedly
// BAD: static List<Object> cache = new ArrayList<>();
// 2. Listeners not removed
// BAD: eventSource.addListener(listener); // never removed
// 3. Unclosed resources
// BAD: InputStream is = new FileInputStream(file); // never closed
// GOOD: Use try-with-resources
// try (var is = new FileInputStream(file)) { ... }
// 4. Custom caches without eviction
// BAD: Map<Key, Value> cache = new HashMap<>(); // grows forever
// GOOD: Use bounded cache with eviction
// Map<Key, Value> cache = Collections.synchronizedMap(
// new LinkedHashMap<>(100, 0.75f, true) {
// protected boolean removeEldestEntry(Map.Entry e) {
// return size() > 100;
// }
// }
// );
IO.println("Memory leak patterns demonstrated (commented code)");
}Use Java Flight Recorder for detailed GC analysis:
# Start recording with GC events
java -XX:StartFlightRecording=duration=60s,filename=recording.jfr \
-jar application.jar
# Or attach to running process
jcmd <pid> JFR.start duration=60s filename=recording.jfr
# Analyze with JDK Mission Control or programmaticallyReduce GC pressure through application design:
| Optimization | Description |
|---|---|
| Object pooling | Reuse objects for frequently allocated types |
| Primitive arrays | Use primitives instead of boxed types |
| StringBuilder | Avoid string concatenation in loops |
| Lazy initialization | Don't create objects until needed |
| Escape analysis | Let JVM optimize short-lived local objects |
| Off-heap storage | Use ByteBuffer for large data sets |
This example demonstrates reducing object allocation:
void main() {
int iterations = 1_000_000;
// Measure high-allocation approach
long start1 = System.nanoTime();
long sum1 = 0;
for (int i = 0; i < iterations; i++) {
// Creates new String each iteration
String s = "Value: " + i;
sum1 += s.length();
}
long time1 = System.nanoTime() - start1;
// Measure low-allocation approach
long start2 = System.nanoTime();
long sum2 = 0;
var sb = new StringBuilder();
for (int i = 0; i < iterations; i++) {
sb.setLength(0); // Reuse StringBuilder
sb.append("Value: ").append(i);
sum2 += sb.length();
}
long time2 = System.nanoTime() - start2;
IO.println("High allocation: " + time1 / 1_000_000 + " ms");
IO.println("Low allocation: " + time2 / 1_000_000 + " ms");
IO.println("Speedup: " + String.format("%.2fx", (double) time1 / time2));
}By reusing the StringBuilder, we eliminate millions of intermediate String
allocations, reducing GC pressure and improving performance.
Garbage collection is fundamental to Java's memory management, and understanding
the available collectors is essential for building high-performance applications.
Modern Java provides a spectrum of collectors suited to different requirements:
G1 GC remains the default and best choice for most applications. It provides
a good balance between throughput and latency, with extensive tuning options for
specific workloads. Its predictable pause times and automatic optimization make
it suitable for everything from web services to batch processing.
ZGC represents the cutting edge of low-latency garbage collection. With
sub-millisecond pause times regardless of heap size, it is ideal for applications
where consistent response times are critical, such as financial trading systems,
real-time analytics, and large-scale data processing.
Shenandoah offers similar low-latency benefits to ZGC with a different
implementation approach. Its concurrent compaction and wide availability make
it a strong choice for applications requiring consistent performance without
the uncertainty of GC pauses.
Epsilon serves a unique niche for testing and benchmarking. By eliminating
GC entirely, it helps developers understand application behavior without GC
interference and is useful for short-lived processes that complete before
exhausting memory.
| Principle | Description |
|---|---|
| Start with defaults | Modern collectors are well-tuned |
| Monitor before tuning | Understand actual behavior first |
| Match GC to requirements | Choose based on latency/throughput needs |
| Enable GC logging | Essential for production debugging |
| Fix application issues first | GC cannot solve memory leaks |
| Test thoroughly | GC behavior varies with workload |
The evolution from early collectors to today's G1, ZGC, and Shenandoah
demonstrates the JVM's continued advancement in handling the demands of
modern applications. By understanding these collectors and following best
practices, developers can build Java applications that are both performant
and reliable.