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.