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.