26.3 GC Tuning and Monitoring

Effective GC tuning requires understanding performance metrics and using appropriate monitoring tools.

GC Performance Metrics

// Understanding GC Metrics
public class GCMetrics {

    public static void printPerformanceMetrics() {
        System.out.println("=== GC PERFORMANCE METRICS ===");

        System.out.println("\n--- PRIMARY METRICS ---");

        System.out.println("\n1. THROUGHPUT");
        System.out.println("Definition: % of time NOT spent in GC");
        System.out.println("Formula: (Total time - GC time) / Total time");
        System.out.println("Example: 95% throughput = 5% time in GC");

        System.out.println("\nGood values:");
        System.out.println("  - >99%: Excellent");
        System.out.println("  - 95-99%: Good");
        System.out.println("  - <95%: May need tuning");

        System.out.println("\n2. PAUSE TIME (LATENCY)");
        System.out.println("Definition: Duration of stop-the-world pauses");
        System.out.println("Metrics:");
        System.out.println("  - Average pause time");
        System.out.println("  - Maximum pause time");
        System.out.println("  - P99 pause time (99th percentile)");

        System.out.println("\nTarget values (depends on SLA):");
        System.out.println("  - Interactive apps: <100ms");
        System.out.println("  - Trading systems: <1ms");
        System.out.println("  - Batch processing: <10s acceptable");

        System.out.println("\n3. MEMORY FOOTPRINT");
        System.out.println("Definition: Total memory used by application + GC");
        System.out.println("Includes:");
        System.out.println("  - Heap memory");
        System.out.println("  - Metaspace");
        System.out.println("  - GC data structures (remembered sets, marking bitmaps)");
        System.out.println("  - Native memory");

        System.out.println("\n--- SECONDARY METRICS ---");

        System.out.println("\n4. ALLOCATION RATE");
        System.out.println("Definition: MB/sec of object allocation");
        System.out.println("High allocation → More frequent GC");
        System.out.println("Monitor: Track to identify allocation hotspots");

        System.out.println("\n5. PROMOTION RATE");
        System.out.println("Definition: MB/sec of objects promoted to Old Gen");
        System.out.println("High promotion → Old Gen fills faster");
        System.out.println("Problem: Premature promotion pollutes Old Gen");

        System.out.println("\n6. GC FREQUENCY");
        System.out.println("Minor GC: Every few seconds (normal)");
        System.out.println("Major GC: Every few minutes to hours");
        System.out.println("Full GC: Should be rare (or never)");

        System.out.println("\n--- TRADE-OFFS ---");
        System.out.println("Cannot optimize all metrics simultaneously:");
        System.out.println("  • Low pause time ↔ High throughput");
        System.out.println("  • Low footprint ↔ High throughput");
        System.out.println("  • Low pause time ↔ Low footprint");
    }
}

GC Logging

// GC Logging Configuration
public class GCLogging {

    public static void printLoggingOptions() {
        System.out.println("=== GC LOGGING (JAVA 9+) ===");

        System.out.println("\n--- UNIFIED LOGGING (-Xlog) ---");

        System.out.println("\nBasic syntax:");
        System.out.println("  -Xlog:<tags>:<output>:<decorators>:<level>");

        System.out.println("\n--- COMMON CONFIGURATIONS ---");

        System.out.println("\n1. Basic GC logging:");
        System.out.println("   -Xlog:gc*:file=gc.log");
        System.out.println("   Logs all GC events to gc.log");

        System.out.println("\n2. Detailed GC logging:");
        System.out.println("   -Xlog:gc*:file=gc.log:time,uptime,level,tags");
        System.out.println("   Includes timestamps, uptime, log level, tags");

        System.out.println("\n3. Separate files by type:");
        System.out.println("   -Xlog:gc:file=gc.log");
        System.out.println("   -Xlog:gc+heap=debug:file=gc-heap.log");

        System.out.println("\n4. Rotating logs:");
        System.out.println("   -Xlog:gc*:file=gc.log::filecount=5,filesize=10M");
        System.out.println("   Keeps 5 files, 10MB each");

        System.out.println("\n5. Console + file:");
        System.out.println("   -Xlog:gc*:stdout");
        System.out.println("   -Xlog:gc*:file=gc.log");

        System.out.println("\n--- G1-SPECIFIC LOGGING ---");
        System.out.println("   -Xlog:gc*,gc+ergo*=trace:file=gc.log");
        System.out.println("   Includes ergonomic decisions");

        System.out.println("\n--- ZGC-SPECIFIC LOGGING ---");
        System.out.println("   -Xlog:gc,gc+heap=info:file=gc.log");

        System.out.println("\n--- SHENANDOAH-SPECIFIC LOGGING ---");
        System.out.println("   -Xlog:gc,gc+ergo=info:file=gc.log");
    }

    public static void printLogAnalysis() {
        System.out.println("\n=== ANALYZING GC LOGS ===");

        System.out.println("\n--- KEY INFORMATION TO EXTRACT ---");

        System.out.println("\n1. GC Pause Times");
        System.out.println("   - Look for 'Pause' events");
        System.out.println("   - Track min/max/average");
        System.out.println("   - Identify outliers");

        System.out.println("\n2. GC Frequency");
        System.out.println("   - Count Minor/Major GC events");
        System.out.println("   - Calculate time between GCs");
        System.out.println("   - Identify patterns");

        System.out.println("\n3. Heap Utilization");
        System.out.println("   - Before/after heap sizes");
        System.out.println("   - Young Gen vs Old Gen usage");
        System.out.println("   - Trends over time");

        System.out.println("\n4. Allocation/Promotion Rates");
        System.out.println("   - Eden growth rate");
        System.out.println("   - Old Gen growth rate");
        System.out.println("   - Identify memory leaks");

        System.out.println("\n--- LOG ANALYSIS TOOLS ---");
        System.out.println("1. GCViewer");
        System.out.println("   - Visual analysis of GC logs");
        System.out.println("   - Charts and statistics");

        System.out.println("\n2. GCEasy (gceasy.io)");
        System.out.println("   - Online log analyzer");
        System.out.println("   - Recommendations");

        System.out.println("\n3. Censum (jClarity)");
        System.out.println("   - Advanced GC analysis");
        System.out.println("   - Production monitoring");
    }
}

Command-Line Monitoring Tools

// jstat - GC Statistics
public class JStatMonitoring {

    public static void printJStatUsage() {
        System.out.println("=== JSTAT - GC STATISTICS ===");

        System.out.println("\n--- BASIC USAGE ---");
        System.out.println("jstat -gc <pid> <interval> <count>");
        System.out.println("  <pid>: Java process ID");
        System.out.println("  <interval>: Sampling interval (ms)");
        System.out.println("  <count>: Number of samples");

        System.out.println("\n--- COMMON OPTIONS ---");

        System.out.println("\n1. Overall GC statistics:");
        System.out.println("   jstat -gc 12345 1000 10");
        System.out.println("   Sample every 1 second, 10 times");
        System.out.println("   Shows heap sizes, GC counts, GC times");

        System.out.println("\n2. GC capacity:");
        System.out.println("   jstat -gccapacity 12345");
        System.out.println("   Min/max sizes of generations");

        System.out.println("\n3. GC cause:");
        System.out.println("   jstat -gccause 12345");
        System.out.println("   Last GC cause and current GC");

        System.out.println("\n4. New generation statistics:");
        System.out.println("   jstat -gcnew 12345 1000");
        System.out.println("   Eden, Survivor spaces");

        System.out.println("\n5. Old generation statistics:");
        System.out.println("   jstat -gcold 12345 1000");
        System.out.println("   Old Gen and Metaspace");

        System.out.println("\n6. GC utilization:");
        System.out.println("   jstat -gcutil 12345 1000");
        System.out.println("   Percentage utilization");

        System.out.println("\n--- INTERPRETING JSTAT OUTPUT ---");

        System.out.println("\nKey columns (jstat -gc):");
        System.out.println("  S0C, S1C: Survivor 0/1 capacity (KB)");
        System.out.println("  S0U, S1U: Survivor 0/1 used (KB)");
        System.out.println("  EC: Eden capacity (KB)");
        System.out.println("  EU: Eden used (KB)");
        System.out.println("  OC: Old Gen capacity (KB)");
        System.out.println("  OU: Old Gen used (KB)");
        System.out.println("  MC: Metaspace capacity (KB)");
        System.out.println("  MU: Metaspace used (KB)");
        System.out.println("  YGC: Young GC count");
        System.out.println("  YGCT: Young GC time (seconds)");
        System.out.println("  FGC: Full GC count");
        System.out.println("  FGCT: Full GC time (seconds)");
        System.out.println("  GCT: Total GC time (seconds)");

        System.out.println("\n--- WHAT TO LOOK FOR ---");
        System.out.println("✓ Steady YGC increase: Normal");
        System.out.println("⚠ Frequent FGC: Problem (tune heap or fix leaks)");
        System.out.println("⚠ OU approaching OC: Old Gen filling up");
        System.out.println("⚠ YGCT/FGCT increasing rapidly: GC taking more time");
    }
}

// jcmd - JVM Diagnostic Commands
class JCmdMonitoring {

    public static void printJCmdUsage() {
        System.out.println("\n=== JCMD - JVM DIAGNOSTIC COMMANDS ===");

        System.out.println("\n--- GC-RELATED COMMANDS ---");

        System.out.println("\n1. List available commands:");
        System.out.println("   jcmd <pid> help");

        System.out.println("\n2. Trigger GC:");
        System.out.println("   jcmd <pid> GC.run");
        System.out.println("   Forces full garbage collection");

        System.out.println("\n3. GC heap info:");
        System.out.println("   jcmd <pid> GC.heap_info");
        System.out.println("   Current heap usage and configuration");

        System.out.println("\n4. Class histogram:");
        System.out.println("   jcmd <pid> GC.class_histogram");
        System.out.println("   Objects by class, count, size");

        System.out.println("\n5. Heap dump:");
        System.out.println("   jcmd <pid> GC.heap_dump /tmp/heap.hprof");
        System.out.println("   Capture heap snapshot");

        System.out.println("\n6. VM flags:");
        System.out.println("   jcmd <pid> VM.flags");
        System.out.println("   All JVM flags (including GC)");

        System.out.println("\n7. VM uptime:");
        System.out.println("   jcmd <pid> VM.uptime");

        System.out.println("\n8. System properties:");
        System.out.println("   jcmd <pid> VM.system_properties");
    }
}

// jmap - Heap Diagnostics
class JMapMonitoring {

    public static void printJMapUsage() {
        System.out.println("\n=== JMAP - HEAP DIAGNOSTICS ===");

        System.out.println("\n--- COMMON COMMANDS ---");

        System.out.println("\n1. Heap summary:");
        System.out.println("   jmap -heap <pid>");
        System.out.println("   GC algorithm, heap config, memory usage");

        System.out.println("\n2. Heap histogram:");
        System.out.println("   jmap -histo <pid>");
        System.out.println("   Object count by class");
        System.out.println("   jmap -histo:live <pid>");
        System.out.println("   Only live objects (triggers GC)");

        System.out.println("\n3. Heap dump:");
        System.out.println("   jmap -dump:live,format=b,file=heap.hprof <pid>");
        System.out.println("   Binary heap dump for analysis");

        System.out.println("\n4. Finalization queue:");
        System.out.println("   jmap -finalizerinfo <pid>");
        System.out.println("   Objects awaiting finalization");

        System.out.println("\n--- ANALYZING HEAP DUMPS ---");
        System.out.println("Tools:");
        System.out.println("  • Eclipse MAT (Memory Analyzer Tool)");
        System.out.println("  • VisualVM");
        System.out.println("  • JProfiler");
        System.out.println("  • YourKit");

        System.out.println("\n⚠ WARNING");
        System.out.println("  - Heap dumps can be large (GB+)");
        System.out.println("  - May pause application briefly");
        System.out.println("  - Use in production with caution");
    }
}

GUI Monitoring Tools

// VisualVM
public class VisualVMMonitoring {

    public static void printVisualVMFeatures() {
        System.out.println("=== VISUALVM ===");

        System.out.println("\n--- OVERVIEW ---");
        System.out.println("Free visual monitoring tool");
        System.out.println("Part of JDK (until Java 9), standalone after");
        System.out.println("Download: visualvm.github.io");

        System.out.println("\n--- GC MONITORING FEATURES ---");

        System.out.println("\n1. Real-time heap monitoring");
        System.out.println("   - Heap size over time");
        System.out.println("   - Used vs allocated");
        System.out.println("   - Generation breakdown");

        System.out.println("\n2. GC activity visualization");
        System.out.println("   - GC events timeline");
        System.out.println("   - Pause times");
        System.out.println("   - Frequency");

        System.out.println("\n3. Heap dump analysis");
        System.out.println("   - Load .hprof files");
        System.out.println("   - Largest objects");
        System.out.println("   - Dominator tree");
        System.out.println("   - OQL (Object Query Language)");

        System.out.println("\n4. Profiling");
        System.out.println("   - CPU profiling");
        System.out.println("   - Memory allocation profiling");
        System.out.println("   - Identify hotspots");

        System.out.println("\n--- PLUGINS ---");
        System.out.println("  • Visual GC: Real-time GC visualization");
        System.out.println("  • BTrace: Dynamic tracing");
        System.out.println("  • Threads: Thread analysis");
    }
}

// JConsole
class JConsoleMonitoring {

    public static void printJConsoleFeatures() {
        System.out.println("\n=== JCONSOLE ===");

        System.out.println("\n--- OVERVIEW ---");
        System.out.println("Built-in JMX monitoring tool");
        System.out.println("Ships with JDK");
        System.out.println("Command: jconsole");

        System.out.println("\n--- GC MONITORING FEATURES ---");

        System.out.println("\n1. Memory tab");
        System.out.println("   - Heap memory graph");
        System.out.println("   - Non-heap memory graph");
        System.out.println("   - Memory pools breakdown");
        System.out.println("   - Perform GC button");

        System.out.println("\n2. MBeans tab");
        System.out.println("   - java.lang:type=Memory");
        System.out.println("   - java.lang:type=GarbageCollector,name=*");
        System.out.println("   - java.lang:type=MemoryPool,name=*");

        System.out.println("\n3. VM Summary tab");
        System.out.println("   - JVM arguments");
        System.out.println("   - GC algorithm");
        System.out.println("   - Heap configuration");

        System.out.println("\n--- REMOTE MONITORING ---");
        System.out.println("Enable JMX:");
        System.out.println("  -Dcom.sun.management.jmxremote");
        System.out.println("  -Dcom.sun.management.jmxremote.port=9999");
        System.out.println("  -Dcom.sun.management.jmxremote.authenticate=false");
        System.out.println("  -Dcom.sun.management.jmxremote.ssl=false");
    }
}

Java Flight Recorder (JFR)

// JFR for GC Analysis
public class JFRMonitoring {

    public static void printJFRUsage() {
        System.out.println("=== JAVA FLIGHT RECORDER (JFR) ===");

        System.out.println("\n--- OVERVIEW ---");
        System.out.println("Low-overhead profiling framework");
        System.out.println("Built into JDK (Java 11+, free in Java 11+)");
        System.out.println("Records JVM events for analysis");

        System.out.println("\n--- STARTING JFR ---");

        System.out.println("\n1. Command-line (at startup):");
        System.out.println("   java -XX:StartFlightRecording=");
        System.out.println("        duration=60s,");
        System.out.println("        filename=recording.jfr MyApp");

        System.out.println("\n2. jcmd (running JVM):");
        System.out.println("   jcmd <pid> JFR.start duration=60s filename=rec.jfr");

        System.out.println("\n3. Dump recording:");
        System.out.println("   jcmd <pid> JFR.dump filename=dump.jfr");

        System.out.println("\n4. Stop recording:");
        System.out.println("   jcmd <pid> JFR.stop");

        System.out.println("\n--- GC-RELATED JFR EVENTS ---");

        System.out.println("\n1. jdk.GarbageCollection");
        System.out.println("   - All GC events");
        System.out.println("   - Duration, type, cause");

        System.out.println("\n2. jdk.G1GarbageCollection");
        System.out.println("   - G1-specific events");

        System.out.println("\n3. jdk.ZGarbageCollection");
        System.out.println("   - ZGC-specific events");

        System.out.println("\n4. jdk.GCHeapSummary");
        System.out.println("   - Heap state before/after GC");

        System.out.println("\n5. jdk.ObjectAllocationInNewTLAB");
        System.out.println("   - TLAB allocations");

        System.out.println("\n6. jdk.ObjectAllocationOutsideTLAB");
        System.out.println("   - Large object allocations");

        System.out.println("\n--- ANALYZING JFR FILES ---");

        System.out.println("\n1. Java Mission Control (JMC)");
        System.out.println("   - Visual analysis tool");
        System.out.println("   - Download: jdk.java.net/jmc");
        System.out.println("   - Open .jfr files");

        System.out.println("\n2. Command-line:");
        System.out.println("   jfr print recording.jfr");
        System.out.println("   jfr summary recording.jfr");

        System.out.println("\n--- JMC ANALYSIS FEATURES ---");
        System.out.println("  • GC timeline and pause times");
        System.out.println("  • Memory allocation hotspots");
        System.out.println("  • CPU usage during GC");
        System.out.println("  • Automated analysis (warnings/suggestions)");
    }
}

Tuning Parameters

// Common GC Tuning Flags
public class GCTuningParameters {

    public static void printHeapSizingFlags() {
        System.out.println("=== HEAP SIZING FLAGS ===");

        System.out.println("\n--- BASIC HEAP SIZE ---");
        System.out.println("-Xms<size>: Initial heap size");
        System.out.println("  Example: -Xms4g");
        System.out.println("-Xmx<size>: Maximum heap size");
        System.out.println("  Example: -Xmx8g");
        System.out.println("⭐ Best practice: Set -Xms equal to -Xmx");
        System.out.println("   Avoids heap resizing overhead");

        System.out.println("\n--- GENERATION SIZING ---");
        System.out.println("-XX:NewSize=<size>: Initial Young Gen size");
        System.out.println("-XX:MaxNewSize=<size>: Maximum Young Gen size");
        System.out.println("-XX:NewRatio=<n>: Old/Young size ratio");
        System.out.println("  Example: -XX:NewRatio=2 → Old is 2x Young");
        System.out.println("-XX:SurvivorRatio=<n>: Eden/Survivor ratio");
        System.out.println("  Example: -XX:SurvivorRatio=8 → Eden is 8x Survivor");

        System.out.println("\n--- METASPACE ---");
        System.out.println("-XX:MetaspaceSize=<size>: Initial metaspace");
        System.out.println("-XX:MaxMetaspaceSize=<size>: Maximum metaspace");
        System.out.println("  Default: Unlimited (grows as needed)");
        System.out.println("  Recommendation: Set limit to prevent runaway growth");
    }

    public static void printG1TuningFlags() {
        System.out.println("\n=== G1 TUNING FLAGS ===");

        System.out.println("\n-XX:MaxGCPauseMillis=<ms>");
        System.out.println("  Pause time goal (not guarantee)");
        System.out.println("  Default: 200ms");
        System.out.println("  Example: -XX:MaxGCPauseMillis=100");

        System.out.println("\n-XX:G1HeapRegionSize=<size>");
        System.out.println("  Region size (1MB - 32MB, power of 2)");
        System.out.println("  Auto-calculated by default");
        System.out.println("  Example: -XX:G1HeapRegionSize=16m");

        System.out.println("\n-XX:InitiatingHeapOccupancyPercent=<n>");
        System.out.println("  Heap % to trigger concurrent marking");
        System.out.println("  Default: 45");
        System.out.println("  Lower → More frequent marking, less Full GC risk");
        System.out.println("  Example: -XX:InitiatingHeapOccupancyPercent=35");

        System.out.println("\n-XX:G1NewSizePercent=<n>");
        System.out.println("  Minimum Young Gen % of heap");
        System.out.println("  Default: 5");

        System.out.println("\n-XX:G1MaxNewSizePercent=<n>");
        System.out.println("  Maximum Young Gen % of heap");
        System.out.println("  Default: 60");

        System.out.println("\n-XX:G1MixedGCCountTarget=<n>");
        System.out.println("  Target number of mixed GCs after marking");
        System.out.println("  Default: 8");
        System.out.println("  Lower → More aggressive Old Gen collection");

        System.out.println("\n-XX:ConcGCThreads=<n>");
        System.out.println("  Concurrent marking threads");
        System.out.println("  Default: ~ParallelGCThreads/4");
    }

    public static void printZGCTuningFlags() {
        System.out.println("\n=== ZGC TUNING FLAGS ===");

        System.out.println("\n-XX:ConcGCThreads=<n>");
        System.out.println("  Concurrent GC threads");
        System.out.println("  Default: 12.5% of cores");
        System.out.println("  Increase for better throughput");

        System.out.println("\n-XX:ZCollectionInterval=<seconds>");
        System.out.println("  Minimum time between GC cycles");
        System.out.println("  Proactive GC (prevents allocation stalls)");
        System.out.println("  Example: -XX:ZCollectionInterval=60");

        System.out.println("\n-XX:ZUncommitDelay=<seconds>");
        System.out.println("  Time before uncommitting unused memory");
        System.out.println("  Default: 300 (5 minutes)");
        System.out.println("  0 = disabled");

        System.out.println("\n-XX:+ZGenerational (Java 21+)");
        System.out.println("  Enable generational ZGC");
        System.out.println("  Improves throughput significantly");
    }

    public static void printCommonFlags() {
        System.out.println("\n=== COMMON GC FLAGS ===");

        System.out.println("\n-XX:ParallelGCThreads=<n>");
        System.out.println("  STW GC threads");
        System.out.println("  Default: # of CPU cores (up to 8), then scaled");

        System.out.println("\n-XX:+DisableExplicitGC");
        System.out.println("  Ignore System.gc() calls");
        System.out.println("  Recommended for production");

        System.out.println("\n-XX:MaxTenuringThreshold=<n>");
        System.out.println("  Survivor age before promotion");
        System.out.println("  Default: 15 (G1: dynamic)");
        System.out.println("  Range: 0-15");

        System.out.println("\n-XX:+AlwaysPreTouch");
        System.out.println("  Touch all heap pages at startup");
        System.out.println("  Prevents lazy allocation delays");
        System.out.println("  Increases startup time");
    }
}

Best Practices

  • Enable GC logging: Always log GC in production.
  • Monitor continuously: Use JFR or monitoring tools.
  • Set -Xms = -Xmx: Avoid heap resizing.
  • Start with defaults: Tune only when needed.
  • Measure before tuning: Establish baseline metrics.
  • Change one parameter at a time: Isolate effects.
  • Test under load: Reproduce production traffic.
  • Monitor allocation rate: High allocation = frequent GC.
  • Watch for Full GC: Should be rare; investigate causes.
  • Use JFR for profiling: Low overhead, comprehensive data.