Garbage Collection is the JVM automatically reclaiming memory occupied by objects that are no longer reachable. Choosing the right GC algorithm is a key production concern — the wrong choice leads to long pauses, unpredictable latency, or poor throughput. Understanding GC roots, generational collection, and each collector's trade-offs is essential for JVM performance tuning.

Key Points

  • GC Roots: the starting points for reachability analysis — stack variables, static fields, JNI references, active threads
  • An object is garbage when no GC root can reach it (transitively) — GC walks the object graph from roots
  • Generational hypothesis: most objects die young — Young Gen (Eden + 2 Survivor spaces) collected frequently with cheap Minor GC
  • Minor GC: copies surviving young-gen objects between Survivor spaces; objects surviving N GCs (default 15) are promoted to Old Gen
  • Major/Full GC: collects Old Gen — typically slower and potentially Stop-The-World
  • Serial GC (-XX:+UseSerialGC): single-threaded, simplest — best for small heaps (<256MB), batch jobs
  • Parallel GC (-XX:+UseParallelGC): multi-threaded, throughput-optimised — Java 8 default, long STW pauses
  • G1 GC (-XX:+UseG1GC): heap divided into ~2000 equal regions, concurrent + incremental — Java 9+ default, predictable pauses
  • ZGC (-XX:+UseZGC): sub-millisecond pauses even on TB heaps, concurrent mark+relocate — Java 15+ production-ready
  • Shenandoah (-XX:+UseShenandoahGC): similar to ZGC, concurrent evacuation — Red Hat, OpenJDK
GCPause typeThroughputLatencyHeap sizeDefault since
SerialSTW all phasesLowHigh<256MBJava 1
ParallelSTW all phasesHighHighMediumJava 8
G1Short concurrent + small STWGoodLow6GB–50GBJava 9
ZGCSub-ms STW (roots only)GoodVery lowUp to TBJava 15
ShenandoahConcurrent evacuationGoodVery lowMedium–largeJava 12

JVM GC tuning flags: G1 with pause target, ZGC for low-latency, GC logging, heap dump on OOM

# Common JVM GC flags
# G1 with 4GB heap, 200ms max pause goal
java -Xms2g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar app.jar

# ZGC for low-latency services
java -Xmx16g -XX:+UseZGC -XX:SoftMaxHeapSize=12g -jar app.jar

# Enable GC logging (Java 11+)
java -Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=20m -jar app.jar

# Print GC summary on exit
java -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar app.jar

# Useful diagnostic flags
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps/
-XX:+PrintGCApplicationStoppedTime   # show STW pause times
-XX:NewRatio=3                        # old:young = 3:1
-XX:SurvivorRatio=8                   # eden:survivor = 8:1

Real-World Example

A 99th-percentile latency spike every few minutes is often a Full GC. First step: enable GC logging and use GCViewer or GCEasy to visualise pauses. If Old Gen fills gradually, you likely have a memory leak (use heap profiler like JFR or async-profiler). If promotions are too fast (young objects promoted to old), tune -XX:NewSize / -XX:MaxNewSize to give Young Gen more space.