26.1 Garbage Collection Fundamentals
Understanding garbage collection fundamentals is essential for optimizing Java application performance and choosing the right collector.
What is Garbage Collection?
// GC Concepts
public class GCFundamentals {
public static void printGCConcepts() {
System.out.println("=== GARBAGE COLLECTION CONCEPTS ===");
System.out.println("\n--- WHAT IS GC? ---");
System.out.println("Automatic memory management");
System.out.println(" - Identifies unreachable objects");
System.out.println(" - Reclaims memory");
System.out.println(" - Prevents memory leaks");
System.out.println(" - Eliminates manual memory management");
System.out.println("\n--- OBJECT LIFECYCLE ---");
System.out.println("1. Object allocated on heap");
System.out.println("2. Referenced by variables, fields, arrays");
System.out.println("3. References removed (out of scope, nulled)");
System.out.println("4. Becomes unreachable");
System.out.println("5. GC identifies as garbage");
System.out.println("6. Memory reclaimed");
System.out.println("\n--- REACHABILITY ---");
System.out.println("GC Roots (starting points):");
System.out.println(" - Local variables in active methods");
System.out.println(" - Static fields");
System.out.println(" - JNI references");
System.out.println(" - Active threads");
System.out.println("\nReachable: Path from GC root exists");
System.out.println("Unreachable: No path from any GC root → garbage");
}
// Example: Object becomes unreachable
public static void demonstrateGarbageCreation() {
System.out.println("\n=== GARBAGE CREATION EXAMPLE ===");
// Create object - reachable
String message = new String("Hello");
System.out.println("Object created and reachable");
// Remove reference - unreachable (eligible for GC)
message = null;
System.out.println("Reference nulled - object eligible for GC");
// Suggest GC (doesn't guarantee immediate collection)
System.gc();
System.out.println("GC suggested (not guaranteed)");
}
}
Generational Hypothesis
// Generational GC Concepts
public class GenerationalGC {
public static void printGenerationalHypothesis() {
System.out.println("=== GENERATIONAL HYPOTHESIS ===");
System.out.println("\n--- KEY OBSERVATIONS ---");
System.out.println("1. Most objects die young");
System.out.println(" - 90%+ of objects become garbage shortly after allocation");
System.out.println(" - Short-lived: temporary objects, loop variables");
System.out.println("\n2. Few objects survive long");
System.out.println(" - <5% of objects survive multiple GC cycles");
System.out.println(" - Long-lived: caches, singletons, static data");
System.out.println("\n--- GENERATIONAL DESIGN ---");
System.out.println("Divide heap into generations:");
System.out.println(" - Young Generation: New objects, frequent GC");
System.out.println(" - Old Generation: Survived objects, infrequent GC");
System.out.println("\n--- BENEFITS ---");
System.out.println("✓ Collect young gen frequently (most garbage there)");
System.out.println("✓ Collect old gen rarely (little garbage there)");
System.out.println("✓ Faster collections (smaller regions)");
System.out.println("✓ Better overall performance");
}
public static void printHeapStructure() {
System.out.println("\n=== HEAP STRUCTURE (GENERATIONAL) ===");
System.out.println("\nYoung Generation (1/3 of heap):");
System.out.println(" - Eden Space (80%)");
System.out.println(" * New objects allocated here");
System.out.println(" - Survivor Space 0 (10%)");
System.out.println(" * From-space or To-space");
System.out.println(" - Survivor Space 1 (10%)");
System.out.println(" * From-space or To-space");
System.out.println("\nOld Generation (2/3 of heap):");
System.out.println(" - Tenured Space");
System.out.println(" * Long-lived objects");
System.out.println(" * Objects that survived multiple minor GCs");
System.out.println("\nMetaspace (native memory, not heap):");
System.out.println(" - Class metadata");
System.out.println(" - Grows automatically");
System.out.println(" - Replaces PermGen (Java 8+)");
}
}
GC Algorithm Types
// GC Algorithm Categories
public class GCAlgorithms {
public static void printAlgorithmTypes() {
System.out.println("=== GC ALGORITHM TYPES ===");
System.out.println("\n--- 1. MARK AND SWEEP ---");
System.out.println("Phase 1 - Mark:");
System.out.println(" - Traverse object graph from GC roots");
System.out.println(" - Mark all reachable objects");
System.out.println("\nPhase 2 - Sweep:");
System.out.println(" - Scan heap");
System.out.println(" - Reclaim unmarked (unreachable) objects");
System.out.println("\nCharacteristics:");
System.out.println(" ✓ Simple algorithm");
System.out.println(" ✗ Heap fragmentation");
System.out.println(" ✗ Requires traversing entire heap");
System.out.println("\n--- 2. COPYING COLLECTOR ---");
System.out.println("Algorithm:");
System.out.println(" - Divide memory into two spaces");
System.out.println(" - Allocate in 'from-space'");
System.out.println(" - Copy live objects to 'to-space'");
System.out.println(" - Swap spaces");
System.out.println("\nCharacteristics:");
System.out.println(" ✓ No fragmentation");
System.out.println(" ✓ Fast allocation (bump pointer)");
System.out.println(" ✗ Requires 2x memory");
System.out.println(" ✓ Used for Young Gen (Eden → Survivor)");
System.out.println("\n--- 3. MARK-SWEEP-COMPACT ---");
System.out.println("Phase 1 - Mark: Identify live objects");
System.out.println("Phase 2 - Sweep: Remove dead objects");
System.out.println("Phase 3 - Compact: Move live objects together");
System.out.println("\nCharacteristics:");
System.out.println(" ✓ No fragmentation");
System.out.println(" ✓ Efficient memory use");
System.out.println(" ✗ Slower than copying");
System.out.println(" ✓ Used for Old Gen");
System.out.println("\n--- 4. CONCURRENT MARKING ---");
System.out.println("Algorithm:");
System.out.println(" - Mark phase runs concurrently with application");
System.out.println(" - Minimal stop-the-world pauses");
System.out.println(" - Uses write barriers to track changes");
System.out.println("\nCharacteristics:");
System.out.println(" ✓ Low pause times");
System.out.println(" ✗ More CPU overhead");
System.out.println(" ✗ More complex");
System.out.println(" ✓ Used in G1, ZGC, Shenandoah");
}
}
GC Phases and Types
// GC Collection Types
public class GCCollectionTypes {
public static void printCollectionTypes() {
System.out.println("=== GC COLLECTION TYPES ===");
System.out.println("\n--- MINOR GC (YOUNG GENERATION) ---");
System.out.println("What:");
System.out.println(" - Collects Young Generation only");
System.out.println(" - Copies live objects from Eden to Survivor");
System.out.println(" - Promotes old survivors to Old Gen");
System.out.println("\nCharacteristics:");
System.out.println(" ✓ Very frequent (every few seconds)");
System.out.println(" ✓ Fast (1-50ms typical)");
System.out.println(" ✓ Stop-the-world");
System.out.println(" ✓ Collects most garbage");
System.out.println("\nTriggers:");
System.out.println(" - Eden space full");
System.out.println(" - Allocation fails in Eden");
System.out.println("\n--- MAJOR GC (OLD GENERATION) ---");
System.out.println("What:");
System.out.println(" - Collects Old Generation");
System.out.println(" - May include Young Gen (Full GC)");
System.out.println("\nCharacteristics:");
System.out.println(" ✓ Infrequent (minutes to hours)");
System.out.println(" ✗ Slow (100ms-10s+)");
System.out.println(" ✗ Stop-the-world (or mostly concurrent)");
System.out.println("\nTriggers:");
System.out.println(" - Old Gen full");
System.out.println(" - Allocation fails in Old Gen");
System.out.println(" - System.gc() called");
System.out.println(" - Metaspace full");
System.out.println("\n--- FULL GC ---");
System.out.println("What:");
System.out.println(" - Collects entire heap (Young + Old + Metaspace)");
System.out.println(" - Most thorough collection");
System.out.println("\nCharacteristics:");
System.out.println(" ✗ Very slow (can be 10s+)");
System.out.println(" ✗ Stop-the-world");
System.out.println(" ⚠ Should be rare in production");
System.out.println("\nTriggers:");
System.out.println(" - Old Gen full with promotion failure");
System.out.println(" - Metaspace expansion");
System.out.println(" - Explicit System.gc()");
System.out.println(" - Heap dump requested");
}
public static void printGCPhases() {
System.out.println("\n=== TYPICAL GC PHASES ===");
System.out.println("\n--- STOP-THE-WORLD GC ---");
System.out.println("1. Stop application threads (STW pause)");
System.out.println("2. Mark live objects");
System.out.println("3. Copy/compact objects");
System.out.println("4. Update references");
System.out.println("5. Resume application threads");
System.out.println("\n--- CONCURRENT GC (G1, ZGC, Shenandoah) ---");
System.out.println("1. Initial mark (STW, brief)");
System.out.println("2. Concurrent mark (parallel with app)");
System.out.println("3. Remark (STW, brief)");
System.out.println("4. Concurrent cleanup (parallel with app)");
System.out.println("5. Evacuation (mostly concurrent or very brief STW)");
}
}
Memory Allocation
// Memory Allocation Patterns
public class MemoryAllocation {
public static void printAllocationPatterns() {
System.out.println("=== MEMORY ALLOCATION ===");
System.out.println("\n--- FAST PATH: TLAB (Thread-Local Allocation Buffer) ---");
System.out.println("What:");
System.out.println(" - Small region of Eden per thread");
System.out.println(" - Lock-free allocation");
System.out.println(" - Bump-the-pointer allocation");
System.out.println("\nProcess:");
System.out.println(" 1. Thread allocates in its TLAB");
System.out.println(" 2. Increment pointer");
System.out.println(" 3. If TLAB full, allocate new TLAB");
System.out.println("\nBenefits:");
System.out.println(" ✓ No synchronization needed");
System.out.println(" ✓ Very fast (few CPU cycles)");
System.out.println(" ✓ Cache-friendly");
System.out.println("\n--- SLOW PATH: SHARED EDEN ---");
System.out.println("Used when:");
System.out.println(" - Object too large for TLAB");
System.out.println(" - TLAB exhausted");
System.out.println("\nProcess:");
System.out.println(" 1. Synchronize on Eden");
System.out.println(" 2. Find free space");
System.out.println(" 3. Allocate object");
System.out.println("\n--- DIRECT OLD GEN ALLOCATION ---");
System.out.println("Used for:");
System.out.println(" - Very large objects");
System.out.println(" - Objects likely to be long-lived");
System.out.println("\nThreshold:");
System.out.println(" - Configurable with -XX:PretenureSizeThreshold");
System.out.println(" - Default varies by collector");
}
// Example: Allocation pressure
public static void demonstrateAllocationPressure() {
System.out.println("\n=== ALLOCATION PRESSURE DEMO ===");
long startTime = System.currentTimeMillis();
long count = 0;
// Create short-lived objects (allocation pressure)
for (int i = 0; i < 10_000_000; i++) {
String temp = "Temporary object " + i;
// temp immediately becomes garbage
count++;
}
long endTime = System.currentTimeMillis();
System.out.println("Created " + count + " objects");
System.out.println("Time: " + (endTime - startTime) + "ms");
System.out.println("Most objects became garbage immediately");
System.out.println("Minor GCs likely occurred during this loop");
}
}
Object Promotion
// Object Promotion Between Generations
public class ObjectPromotion {
public static void printPromotionProcess() {
System.out.println("=== OBJECT PROMOTION ===");
System.out.println("\n--- PROMOTION PROCESS ---");
System.out.println("1. Object allocated in Eden");
System.out.println("2. Survives first Minor GC → Survivor 0 (age = 1)");
System.out.println("3. Survives second Minor GC → Survivor 1 (age = 2)");
System.out.println("4. Continues surviving → switches survivors");
System.out.println("5. Reaches threshold age → promoted to Old Gen");
System.out.println("\n--- TENURING THRESHOLD ---");
System.out.println("Default: -XX:MaxTenuringThreshold=15");
System.out.println(" - Objects surviving 15 Minor GCs promoted");
System.out.println(" - Can be adjusted (1-15)");
System.out.println(" - Lower = earlier promotion");
System.out.println(" - Higher = more survivor copies");
System.out.println("\n--- DYNAMIC THRESHOLD ---");
System.out.println("JVM may lower threshold if:");
System.out.println(" - Survivor spaces filling up");
System.out.println(" - Too many objects surviving");
System.out.println(" - Prevents survivor overflow");
System.out.println("\n--- PREMATURE PROMOTION ---");
System.out.println("Problem:");
System.out.println(" - Objects promoted too early");
System.out.println(" - Still die shortly after promotion");
System.out.println(" - Pollutes Old Gen with garbage");
System.out.println("\nCauses:");
System.out.println(" - Survivor spaces too small");
System.out.println(" - Tenuring threshold too low");
System.out.println(" - High allocation rate");
System.out.println("\nSolutions:");
System.out.println(" - Increase survivor space");
System.out.println(" - Increase tenuring threshold");
System.out.println(" - Reduce allocation rate");
}
}
GC Roots and Reachability
// GC Roots and Object Reachability
public class GCRootsAndReachability {
public static void printGCRoots() {
System.out.println("=== GC ROOTS ===");
System.out.println("\n--- TYPES OF GC ROOTS ---");
System.out.println("\n1. Local Variables:");
System.out.println(" - Variables in active method stack frames");
System.out.println(" - Cleared when method returns");
System.out.println("\n2. Static Fields:");
System.out.println(" - Class static variables");
System.out.println(" - Live as long as class is loaded");
System.out.println("\n3. Active Threads:");
System.out.println(" - Thread objects themselves");
System.out.println(" - Thread-local variables");
System.out.println("\n4. JNI References:");
System.out.println(" - Native code references");
System.out.println(" - Must be explicitly released");
System.out.println("\n5. Synchronization Monitors:");
System.out.println(" - Objects used in synchronized blocks");
System.out.println(" - Lock objects");
}
// Example: Strong vs Weak references
public static void demonstrateReferenceTypes() {
System.out.println("\n=== REFERENCE TYPES ===");
// Strong reference (prevents GC)
Object strongRef = new Object();
System.out.println("Strong reference: Never GC'd while reachable");
// Weak reference (allows GC)
java.lang.ref.WeakReference<Object> weakRef =
new java.lang.ref.WeakReference<>(new Object());
System.out.println("Weak reference: GC'd when weakly reachable");
// Soft reference (GC'd under memory pressure)
java.lang.ref.SoftReference<Object> softRef =
new java.lang.ref.SoftReference<>(new Object());
System.out.println("Soft reference: GC'd under memory pressure");
// Phantom reference (post-mortem cleanup)
java.lang.ref.PhantomReference<Object> phantomRef =
new java.lang.ref.PhantomReference<>(
new Object(),
new java.lang.ref.ReferenceQueue<>()
);
System.out.println("Phantom reference: For cleanup after GC");
}
}
Best Practices
- Understand the heap: Know Young/Old gen structure and behavior.
- Monitor GC activity: Use logging and monitoring tools.
- Minimize allocations: Reduce garbage creation rate.
- Object pooling carefully: Only for expensive objects.
- Avoid premature optimization: Profile before tuning.
- Size heap appropriately: Set -Xms equal to -Xmx.
- Choose right collector: Match collector to workload.
- Test under load: Reproduce production conditions.
- Understand reference types: Use weak/soft refs for caches.
- Avoid System.gc(): Let JVM manage GC timing.