26.2 Modern Garbage Collectors
Modern garbage collectors offer different trade-offs between throughput, pause times, and memory overhead.
G1 Garbage Collector
// G1 GC Characteristics
public class G1GarbageCollector {
public static void printG1Characteristics() {
System.out.println("=== G1 GARBAGE COLLECTOR ===");
System.out.println("\n--- OVERVIEW ---");
System.out.println("Name: Garbage-First Collector");
System.out.println("Default: Since Java 9");
System.out.println("Target: Server applications with large heaps");
System.out.println("Goal: Predictable pause times");
System.out.println("\n--- KEY FEATURES ---");
System.out.println("✓ Region-based heap layout");
System.out.println("✓ Incremental compaction");
System.out.println("✓ Predictable pause time goals");
System.out.println("✓ Concurrent marking");
System.out.println("✓ Mixed collections (Young + Old)");
System.out.println("\n--- REGION STRUCTURE ---");
System.out.println("Heap divided into ~2048 regions");
System.out.println("Region size: 1MB - 32MB (power of 2)");
System.out.println(" - Calculated from heap size");
System.out.println(" - Larger heap → larger regions");
System.out.println("\nRegion Types:");
System.out.println(" - Eden regions (young objects)");
System.out.println(" - Survivor regions (survived minor GC)");
System.out.println(" - Old regions (tenured objects)");
System.out.println(" - Humongous regions (large objects >= 50% region)");
System.out.println(" - Free regions (available for allocation)");
System.out.println("\n--- GARBAGE-FIRST STRATEGY ---");
System.out.println("1. Mark phase: Identify live/dead objects per region");
System.out.println("2. Calculate garbage %: Which regions have most garbage");
System.out.println("3. Prioritize: Collect regions with most garbage first");
System.out.println("4. Meet pause goal: Stop when time budget exhausted");
}
}
// G1 Configuration
class G1Configuration {
/*
# Enable G1 (default in Java 9+)
java -XX:+UseG1GC MyApp
# Set pause time goal (default 200ms)
java -XX:MaxGCPauseMillis=100 MyApp
# Set heap size
java -Xms8g -Xmx8g -XX:+UseG1GC MyApp
# Set region size explicitly (usually auto-calculated)
java -XX:G1HeapRegionSize=16m -XX:+UseG1GC MyApp
# Control concurrent marking threshold
java -XX:InitiatingHeapOccupancyPercent=45 -XX:+UseG1GC MyApp
# Set Young Gen size range
java -XX:G1NewSizePercent=5 -XX:G1MaxNewSizePercent=60 -XX:+UseG1GC MyApp
# Control mixed GC behavior
java -XX:G1MixedGCCountTarget=8 -XX:+UseG1GC MyApp
*/
}
G1 GC Phases
// G1 Collection Phases
public class G1Phases {
public static void printG1Phases() {
System.out.println("=== G1 COLLECTION PHASES ===");
System.out.println("\n--- 1. YOUNG COLLECTION (MINOR GC) ---");
System.out.println("Trigger: Eden regions full");
System.out.println("Process:");
System.out.println(" 1. Stop-the-world pause");
System.out.println(" 2. Scan GC roots");
System.out.println(" 3. Copy live objects from Eden → Survivor");
System.out.println(" 4. Promote old survivors → Old regions");
System.out.println(" 5. Update references");
System.out.println(" 6. Reclaim Eden regions");
System.out.println("\nCharacteristics:");
System.out.println(" - STW pause (10-50ms typical)");
System.out.println(" - Very frequent");
System.out.println(" - Collects only Young regions");
System.out.println("\n--- 2. CONCURRENT MARKING CYCLE ---");
System.out.println("Trigger: Heap occupancy reaches threshold (IHOP)");
System.out.println(" Default: 45% of total heap");
System.out.println("\nPhases:");
System.out.println(" 2a. Initial Mark (STW, brief)");
System.out.println(" - Piggybacks on Young GC");
System.out.println(" - Marks GC roots");
System.out.println(" 2b. Concurrent Mark (parallel with app)");
System.out.println(" - Traverses object graph");
System.out.println(" - Marks reachable objects");
System.out.println(" 2c. Remark (STW, brief)");
System.out.println(" - Finalizes marking");
System.out.println(" - Processes references");
System.out.println(" 2d. Cleanup (STW, brief)");
System.out.println(" - Identifies empty regions");
System.out.println(" - Prepares for mixed GC");
System.out.println("\n--- 3. MIXED COLLECTION ---");
System.out.println("Follows concurrent marking cycle");
System.out.println("Process:");
System.out.println(" 1. Stop-the-world pause");
System.out.println(" 2. Collect Young regions (like Minor GC)");
System.out.println(" 3. Collect Old regions with most garbage");
System.out.println(" 4. Evacuate and compact selected regions");
System.out.println(" 5. Update references");
System.out.println("\nCharacteristics:");
System.out.println(" - STW pause (controlled by MaxGCPauseMillis)");
System.out.println(" - Collects Young + subset of Old regions");
System.out.println(" - Multiple mixed GCs after marking cycle");
System.out.println(" - Incremental Old Gen cleanup");
System.out.println("\n--- 4. FULL GC (FALLBACK) ---");
System.out.println("Trigger: Heap exhausted, allocation failure");
System.out.println("Process:");
System.out.println(" - Stop-the-world");
System.out.println(" - Mark entire heap");
System.out.println(" - Compact entire heap");
System.out.println(" - Very expensive (seconds+)");
System.out.println("\n⚠ SHOULD BE RARE");
System.out.println("If frequent Full GCs:");
System.out.println(" - Increase heap size");
System.out.println(" - Lower pause time goal");
System.out.println(" - Reduce allocation rate");
}
}
ZGC (Z Garbage Collector)
// ZGC Characteristics
public class ZGarbageCollector {
public static void printZGCCharacteristics() {
System.out.println("=== Z GARBAGE COLLECTOR (ZGC) ===");
System.out.println("\n--- OVERVIEW ---");
System.out.println("Introduced: Java 11 (experimental), Java 15 (production)");
System.out.println("Target: Ultra-low latency applications");
System.out.println("Goal: Sub-millisecond pause times");
System.out.println("\n--- KEY FEATURES ---");
System.out.println("✓ Sub-millisecond pauses (<1ms typical)");
System.out.println("✓ Concurrent marking, compaction, reference processing");
System.out.println("✓ Scalable (8MB - 16TB+ heaps)");
System.out.println("✓ Colored pointers (metadata in references)");
System.out.println("✓ Load barriers (runtime checks)");
System.out.println("✓ Generational mode (Java 21+)");
System.out.println("\n--- COLORED POINTERS ---");
System.out.println("64-bit reference layout:");
System.out.println(" [18 bits unused] [4 bits metadata] [42 bits address]");
System.out.println("\nMetadata bits:");
System.out.println(" - Finalizable bit");
System.out.println(" - Remapped bit");
System.out.println(" - Marked0 bit");
System.out.println(" - Marked1 bit");
System.out.println("\nBenefits:");
System.out.println(" ✓ GC state stored in reference itself");
System.out.println(" ✓ No object header overhead");
System.out.println(" ✓ Multi-mapping: Physical page mapped to multiple addresses");
System.out.println("\n--- LOAD BARRIERS ---");
System.out.println("What: Checks inserted on every object load");
System.out.println("Purpose: Detect outdated references");
System.out.println("\nProcess:");
System.out.println(" 1. Application loads reference");
System.out.println(" 2. Load barrier checks metadata bits");
System.out.println(" 3. If outdated: Remap to new location");
System.out.println(" 4. If relocated: Return new address");
System.out.println(" 5. Continue execution");
System.out.println("\nCost:");
System.out.println(" - Small CPU overhead per load (~5-15%)");
System.out.println(" - Trade-off for extremely low pause times");
}
}
// ZGC Configuration
class ZGCConfiguration {
/*
# Enable ZGC
java -XX:+UseZGC MyApp
# Set heap size (ZGC supports very large heaps)
java -Xms16g -Xmx16g -XX:+UseZGC MyApp
# Enable generational mode (Java 21+, improves throughput)
java -XX:+UseZGC -XX:+ZGenerational MyApp
# Set concurrent GC threads
java -XX:ConcGCThreads=4 -XX:+UseZGC MyApp
# Proactive GC (start GC before allocation failure)
java -XX:ZCollectionInterval=60 -XX:+UseZGC MyApp
# Uncommit unused memory
java -XX:ZUncommitDelay=300 -XX:+UseZGC MyApp
*/
}
ZGC Phases
// ZGC Collection Phases
public class ZGCPhases {
public static void printZGCPhases() {
System.out.println("=== ZGC COLLECTION PHASES ===");
System.out.println("\n--- ALL PHASES EXCEPT PAUSES ARE CONCURRENT ---");
System.out.println("\n1. Pause Mark Start (STW, <1ms)");
System.out.println(" - Mark GC roots");
System.out.println(" - Start concurrent marking");
System.out.println("\n2. Concurrent Mark/Remap");
System.out.println(" - Traverse object graph");
System.out.println(" - Mark live objects");
System.out.println(" - Application continues running");
System.out.println("\n3. Pause Mark End (STW, <1ms)");
System.out.println(" - Finalize marking");
System.out.println(" - Process weak references");
System.out.println("\n4. Concurrent Prepare for Relocate");
System.out.println(" - Select pages to compact");
System.out.println(" - Build relocation sets");
System.out.println("\n5. Pause Relocate Start (STW, <1ms)");
System.out.println(" - Relocate GC roots");
System.out.println("\n6. Concurrent Relocate");
System.out.println(" - Move objects to new pages");
System.out.println(" - Update colored pointers");
System.out.println(" - Load barriers handle stale references");
System.out.println(" - Application continues running");
System.out.println("\n--- TOTAL STW TIME: <1ms ---");
System.out.println("Breakdown:");
System.out.println(" - Mark Start: ~0.1-0.5ms");
System.out.println(" - Mark End: ~0.1-0.5ms");
System.out.println(" - Relocate Start: ~0.1-0.5ms");
System.out.println(" - Total: ~0.3-1.5ms regardless of heap size");
}
}
Shenandoah GC
// Shenandoah Characteristics
public class ShenandoahGC {
public static void printShenandoahCharacteristics() {
System.out.println("=== SHENANDOAH GARBAGE COLLECTOR ===");
System.out.println("\n--- OVERVIEW ---");
System.out.println("Introduced: Java 12 (experimental), Java 15 (production)");
System.out.println("Target: Low-latency applications");
System.out.println("Goal: Pause times <10ms");
System.out.println("\n--- KEY FEATURES ---");
System.out.println("✓ Concurrent evacuation (concurrent compaction)");
System.out.println("✓ Brooks forwarding pointers");
System.out.println("✓ Load reference barriers");
System.out.println("✓ Pause times independent of heap size");
System.out.println("✓ Region-based like G1");
System.out.println("\n--- BROOKS FORWARDING POINTERS ---");
System.out.println("What: Extra word in object header");
System.out.println("Purpose: Track object relocations");
System.out.println("\nStructure:");
System.out.println(" [Standard object header]");
System.out.println(" [Forwarding pointer] ← points to new location if moved");
System.out.println(" [Object fields]");
System.out.println("\nDuring evacuation:");
System.out.println(" 1. Object moved to new location");
System.out.println(" 2. Forwarding pointer updated to new address");
System.out.println(" 3. Old copy remains (tombstone)");
System.out.println(" 4. Load barriers redirect to new location");
System.out.println("\nTrade-off:");
System.out.println(" ✗ Extra memory per object (1 word)");
System.out.println(" ✓ Enables concurrent evacuation");
System.out.println("\n--- LOAD REFERENCE BARRIERS ---");
System.out.println("What: Checks on every reference load");
System.out.println("Purpose: Follow forwarding pointers");
System.out.println("\nProcess:");
System.out.println(" 1. Load reference from field/array");
System.out.println(" 2. Check forwarding pointer");
System.out.println(" 3. If forwarded: Return new address");
System.out.println(" 4. If not forwarded: Return original");
System.out.println("\nCost:");
System.out.println(" - ~5-10% throughput overhead");
System.out.println(" - Trade-off for low pause times");
}
}
// Shenandoah Configuration
class ShenandoahConfiguration {
/*
# Enable Shenandoah
java -XX:+UseShenandoahGC MyApp
# Set heap size
java -Xms16g -Xmx16g -XX:+UseShenandoahGC MyApp
# Set concurrent GC threads
java -XX:ConcGCThreads=4 -XX:+UseShenandoahGC MyApp
# Heuristics mode
java -XX:ShenandoahGCHeuristics=adaptive -XX:+UseShenandoahGC MyApp
# Options: adaptive, static, compact, aggressive
# Uncommit unused memory
java -XX:ShenandoahUncommitDelay=5000 -XX:+UseShenandoahGC MyApp
*/
}
Shenandoah Phases
// Shenandoah Collection Phases
public class ShenandoahPhases {
public static void printShenandoahPhases() {
System.out.println("=== SHENANDOAH COLLECTION PHASES ===");
System.out.println("\n--- MOSTLY CONCURRENT PHASES ---");
System.out.println("\n1. Init Mark (STW, <1ms)");
System.out.println(" - Mark GC roots");
System.out.println(" - Start concurrent marking");
System.out.println("\n2. Concurrent Marking");
System.out.println(" - Traverse object graph");
System.out.println(" - Mark reachable objects");
System.out.println(" - Application continues");
System.out.println("\n3. Final Mark (STW, 1-5ms)");
System.out.println(" - Drain marking queues");
System.out.println(" - Process weak references");
System.out.println("\n4. Concurrent Cleanup");
System.out.println(" - Reclaim empty regions immediately");
System.out.println("\n5. Concurrent Evacuation");
System.out.println(" ✓ KEY FEATURE: Concurrent compaction");
System.out.println(" - Copy objects to new regions");
System.out.println(" - Update forwarding pointers");
System.out.println(" - Application continues running");
System.out.println(" - Load barriers handle concurrent moves");
System.out.println("\n6. Init Update Refs (STW, <1ms)");
System.out.println(" - Prepare for reference update");
System.out.println("\n7. Concurrent Update References");
System.out.println(" - Update all references to moved objects");
System.out.println(" - Application continues");
System.out.println("\n8. Final Update Refs (STW, 1-5ms)");
System.out.println(" - Finish reference updates");
System.out.println(" - Update GC roots");
System.out.println("\n9. Concurrent Cleanup");
System.out.println(" - Reclaim old regions");
System.out.println("\n--- TOTAL STW TIME: ~2-10ms ---");
}
}
Serial and Parallel GC
// Traditional Collectors
public class TraditionalCollectors {
public static void printSerialGC() {
System.out.println("=== SERIAL GARBAGE COLLECTOR ===");
System.out.println("\n--- OVERVIEW ---");
System.out.println("Oldest collector");
System.out.println("Target: Small heaps (<100MB), single-core systems");
System.out.println("Algorithm: Stop-the-world, single-threaded");
System.out.println("\n--- CHARACTERISTICS ---");
System.out.println("Young Gen: Copying collector");
System.out.println("Old Gen: Mark-sweep-compact");
System.out.println(" ✓ Simplest collector");
System.out.println(" ✓ Lowest memory overhead");
System.out.println(" ✗ Long pauses (single-threaded)");
System.out.println(" ✗ Doesn't use multiple cores");
System.out.println("\n--- WHEN TO USE ---");
System.out.println("✓ Client applications");
System.out.println("✓ Small heaps (<100MB)");
System.out.println("✓ Single-core containers");
System.out.println("✓ Embedded systems");
System.out.println("✗ Server applications");
System.out.println("✗ Multi-core systems");
System.out.println("\n--- CONFIGURATION ---");
System.out.println("Enable:");
System.out.println(" java -XX:+UseSerialGC MyApp");
}
public static void printParallelGC() {
System.out.println("\n=== PARALLEL GARBAGE COLLECTOR ===");
System.out.println("\n--- OVERVIEW ---");
System.out.println("Also called: Throughput Collector");
System.out.println("Default: Java 8");
System.out.println("Target: Batch processing, high throughput");
System.out.println("Algorithm: Stop-the-world, multi-threaded");
System.out.println("\n--- CHARACTERISTICS ---");
System.out.println("Young Gen: Parallel copying");
System.out.println("Old Gen: Parallel mark-sweep-compact");
System.out.println(" ✓ High throughput");
System.out.println(" ✓ Efficient CPU usage");
System.out.println(" ✗ Long pauses (can be seconds)");
System.out.println(" ✗ No pause time goals");
System.out.println("\n--- WHEN TO USE ---");
System.out.println("✓ Batch processing");
System.out.println("✓ Scientific computing");
System.out.println("✓ Data analytics");
System.out.println("✓ Throughput > latency");
System.out.println("✗ Interactive applications");
System.out.println("✗ Latency-sensitive apps");
System.out.println("\n--- CONFIGURATION ---");
System.out.println("Enable:");
System.out.println(" java -XX:+UseParallelGC MyApp");
System.out.println("Set GC threads:");
System.out.println(" java -XX:ParallelGCThreads=8 -XX:+UseParallelGC MyApp");
}
}
Epsilon GC (No-Op Collector)
// Epsilon GC
public class EpsilonGC {
public static void printEpsilonCharacteristics() {
System.out.println("=== EPSILON GARBAGE COLLECTOR ===");
System.out.println("\n--- OVERVIEW ---");
System.out.println("Introduced: Java 11");
System.out.println("Type: No-op collector");
System.out.println("Purpose: No garbage collection");
System.out.println("\n--- CHARACTERISTICS ---");
System.out.println("✓ Handles allocation only");
System.out.println("✓ Never reclaims memory");
System.out.println("✓ Zero GC overhead");
System.out.println("✗ OutOfMemoryError when heap exhausted");
System.out.println("\n--- USE CASES ---");
System.out.println("1. Ultra-short-lived applications");
System.out.println(" - Job completes before heap fills");
System.out.println(" - Startup/shutdown testing");
System.out.println("\n2. Performance testing");
System.out.println(" - Measure GC overhead (compare with/without)");
System.out.println(" - Allocation pressure analysis");
System.out.println("\n3. Memory footprint testing");
System.out.println(" - Exact memory consumption measurement");
System.out.println(" - No GC interference");
System.out.println("\n4. GC-sensitive latency testing");
System.out.println(" - Verify if GC causes latency issues");
System.out.println("\n--- CONFIGURATION ---");
System.out.println("Enable:");
System.out.println(" java -XX:+UnlockExperimentalVMOptions");
System.out.println(" -XX:+UseEpsilonGC MyApp");
System.out.println("\n⚠ NOT FOR PRODUCTION");
System.out.println("Will crash with OutOfMemoryError when heap full");
}
}
Collector Selection
// Choosing the Right Collector
public class CollectorSelection {
public static void printSelectionCriteria() {
System.out.println("=== COLLECTOR SELECTION GUIDE ===");
System.out.println("\n--- SELECTION MATRIX ---");
System.out.println();
System.out.println("| Workload | Heap Size | Pause Target | Throughput | Recommended |");
System.out.println("|------------------------|------------|--------------|------------|----------------|");
System.out.println("| Interactive web app | 4-64 GB | <200ms | Balanced | G1 (default) |");
System.out.println("| Low-latency trading | Any | <1ms | Good | ZGC |");
System.out.println("| Microservices | 512MB-8GB | <50ms | Good | ZGC/Shenandoah |");
System.out.println("| Batch processing | Any | Not critical | Maximum | Parallel |");
System.out.println("| Small client apps | <100MB | Not critical | N/A | Serial |");
System.out.println("| Massive heap (>100GB) | 100GB+ | <10ms | Good | ZGC |");
System.out.println("| Short-lived jobs | Any | N/A | Maximum | Epsilon |");
System.out.println("\n--- DECISION TREE ---");
System.out.println();
System.out.println("START: What is your primary concern?");
System.out.println();
System.out.println("├─ Pause times are critical");
System.out.println("│ ├─ Need <1ms pauses");
System.out.println("│ │ └─→ ZGC (Java 15+)");
System.out.println("│ ├─ Need <10ms pauses");
System.out.println("│ │ └─→ ZGC or Shenandoah (Java 15+)");
System.out.println("│ └─ Need <200ms pauses");
System.out.println("│ └─→ G1 (default, well-tested)");
System.out.println("│");
System.out.println("├─ Maximum throughput (batch jobs)");
System.out.println("│ └─→ Parallel GC");
System.out.println("│");
System.out.println("├─ Very small heap (<100MB)");
System.out.println("│ └─→ Serial GC");
System.out.println("│");
System.out.println("├─ Short-lived application (testing)");
System.out.println("│ └─→ Epsilon GC");
System.out.println("│");
System.out.println("└─ General server application");
System.out.println(" └─→ G1 (default choice)");
}
public static void printComparisonTable() {
System.out.println("\n=== COLLECTOR COMPARISON ===");
System.out.println();
System.out.println("| Collector | Pause Time | Throughput | Heap Overhead | CPU Overhead | Complexity |");
System.out.println("|-------------|------------|------------|---------------|--------------|------------|");
System.out.println("| Serial | High | Medium | Lowest | Lowest | Lowest |");
System.out.println("| Parallel | High | Highest | Low | Low | Low |");
System.out.println("| G1 | Medium | Good | Medium | Medium | Medium |");
System.out.println("| ZGC | Lowest | Good | Medium | Medium-High | High |");
System.out.println("| Shenandoah | Very Low | Good | High | Medium-High | High |");
System.out.println("| Epsilon | None | N/A | None | None | Lowest |");
}
}
Best Practices
- Start with G1: Default collector, well-tested, good for most workloads.
- Profile before switching: Measure actual pause times before changing collectors.
- ZGC for ultra-low latency: When pause times <1ms are critical.
- Shenandoah alternative: Similar to ZGC, different trade-offs.
- Parallel for batch: When throughput matters more than latency.
- Test under load: Reproduce production traffic patterns.
- Monitor GC behavior: Use logging and profiling tools.
- Size heap appropriately: Set -Xms equal to -Xmx for stability.
- Don't over-tune: Start simple, tune only if needed.
- Consider generational ZGC: Java 21+ improves ZGC throughput significantly.