Java's garbage collection automatically manages memory by reclaiming memory occupied by objects that are no longer in use. Here's a comprehensive overview of GC algorithms in Java.
1. GC Generations and Heap Structure
public class HeapStructure { public static void main(String[] args) { // JVM Heap is divided into generations: // - Young Generation (Eden + Survivor Spaces) // - Old Generation (Tenured) // - Permanent Generation / Metaspace (Java 8+) Runtime runtime = Runtime.getRuntime(); System.out.println("Max Memory: " + runtime.maxMemory() / (1024 * 1024) + " MB"); System.out.println("Total Memory: " + runtime.totalMemory() / (1024 * 1024) + " MB"); System.out.println("Free Memory: " + runtime.freeMemory() / (1024 * 1024) + " MB"); // Object allocation demo for (int i = 0; i < 100000; i++) { new Object(); // Most will die young } System.gc(); // Suggest GC (not guaranteed) } } 2. Mark and Sweep Algorithm
Concept: The fundamental GC algorithm used by most collectors
public class MarkSweepDemo { // Mark Phase: Identify live objects // Sweep Phase: Reclaim memory from dead objects public static void main(String[] args) { Object obj1 = new Object(); // Live reference Object obj2 = new Object(); // Live reference // Create circular reference obj1 = obj2; obj2 = obj1; // Even with circular references, GC can collect them // when they become unreachable from GC roots obj1 = null; obj2 = null; System.gc(); // Mark-Sweep will collect these } } // GC Roots include: // - Local variables in active methods // - Static variables // - Active threads // - JNI references 3. Serial Garbage Collector
Use Case: Single-threaded, suitable for small applications
public class SerialGCDemo { // Enable with: -XX:+UseSerialGC public static void createGarbage() { List<byte[]> garbage = new ArrayList<>(); for (int i = 0; i < 100; i++) { garbage.add(new byte[1024 * 1024]); // 1MB each if (i % 10 == 0) { garbage.clear(); // Create some garbage } } } public static void main(String[] args) { System.out.println("Serial GC Demo"); for (int i = 0; i < 5; i++) { createGarbage(); System.gc(); try { Thread.sleep(1000); } catch (InterruptedException e) {} } } } 4. Parallel Garbage Collector (Throughput Collector)
Use Case: Multi-threaded, suitable for batch processing
public class ParallelGCDemo { // Enable with: -XX:+UseParallelGC // Tuning options: // -XX:ParallelGCThreads=N // -XX:MaxGCPauseMillis=N // -XX:GCTimeRatio=N public static void main(String[] args) { System.out.println("Parallel GC Demo"); // Create memory pressure ExecutorService executor = Executors.newFixedThreadPool(4); for (int i = 0; i < 1000; i++) { executor.submit(() -> { byte[] data = new byte[1024 * 100]; // 100KB // Short-lived object }); } executor.shutdown(); } } 5. CMS (Concurrent Mark Sweep) Collector
Use Case: Low pause times, suitable for responsive applications
public class CMSGCDemo { // Enable with: -XX:+UseConcMarkSweepGC // Deprecated in Java 9, removed in Java 14 public static void main(String[] args) { System.out.println("CMS GC Demo - Responsive Application Simulation"); // Simulate application that needs low GC pauses Thread uiThread = new Thread(() -> { while (true) { // Simulate UI rendering renderFrame(); try { Thread.sleep(16); // ~60 FPS } catch (InterruptedException e) { break; } } }); Thread dataThread = new Thread(() -> { while (true) { // Simulate data processing creating garbage processData(); try { Thread.sleep(100); } catch (InterruptedException e) { break; } } }); uiThread.start(); dataThread.start(); } private static void renderFrame() { // Simulate UI rendering - sensitive to pauses Object[] frameData = new Object[1000]; // Short-lived objects } private static void processData() { // Simulate data processing - creates garbage List<byte[]> tempData = new ArrayList<>(); for (int i = 0; i < 100; i++) { tempData.add(new byte[1024]); // 1KB objects } } } 6. G1 (Garbage First) Collector
Use Case: Balanced throughput and pause times, default since Java 9
public class G1GCDemo { // Enable with: -XX:+UseG1GC (default since Java 9) // Tuning options: // -XX:MaxGCPauseMillis=200 // -XX:G1HeapRegionSize=N public static void main(String[] args) { System.out.println("G1 GC Demo"); // G1 divides heap into equal-sized regions // Collects regions with most garbage first // Mixed collections include both young and old regions simulateMixedWorkload(); } private static void simulateMixedWorkload() { // Mix of short-lived and long-lived objects List<Object> longLived = new ArrayList<>(); List<Object> shortLived = new ArrayList<>(); for (int i = 0; i < 100000; i++) { // Some objects survive longer if (i % 100 == 0) { longLived.add(new byte[1024 * 10]); // 10KB } // Many short-lived objects shortLived.add(new byte[1024]); // 1KB if (shortLived.size() > 1000) { shortLived.clear(); // Create garbage } } } } 7. ZGC (Z Garbage Collector)
Use Case: Very low pause times (<10ms), scalable
public class ZGCDemo { // Enable with: -XX:+UseZGC // Requires: Java 11+ (production since Java 15) public static void main(String[] args) { System.out.println("ZGC Demo - Large Heap, Low Pause"); // ZGC can handle very large heaps with minimal pause times final long HEAP_SIZE = 16L * 1024 * 1024 * 1024; // 16GB // Simulate large memory allocation List<byte[]> largeData = new ArrayList<>(); for (int i = 0; i < 1000; i++) { largeData.add(new byte[1024 * 1024]); // 1MB objects if (i % 100 == 0) { // Concurrent processing while GC runs processConcurrently(); } } } private static void processConcurrently() { // ZGC does most work concurrently with application Thread processor = new Thread(() -> { byte[] data = new byte[1024 * 1024]; // 1MB // Process data while GC might be running }); processor.start(); } } 8. Shenandoah GC
Use Case: Low pause times, concurrent compaction
public class ShenandoahGCDemo { // Enable with: -XX:+UseShenandoahGC public static void main(String[] args) { System.out.println("Shenandoah GC Demo - Concurrent Compaction"); // Shenandoah does compaction concurrently with application simulateMemoryFragmentation(); } private static void simulateMemoryFragmentation() { // Create alternating pattern of different sized objects List<Object> fragmentedHeap = new ArrayList<>(); for (int i = 0; i < 10000; i++) { if (i % 2 == 0) { fragmentedHeap.add(new byte[1024 * 64]); // 64KB } else { fragmentedHeap.add(new byte[1024 * 16]); // 16KB } // Remove some objects to create fragmentation if (i % 10 == 0) { fragmentedHeap.remove(fragmentedHeap.size() - 1); } } } } 9. GC Monitoring and Analysis
public class GCMonitoring { public static void main(String[] args) throws InterruptedException { // Enable GC logging: -Xlog:gc* // Or: -XX:+PrintGCDetails -XX:+PrintGCDateStamps System.out.println("Monitoring GC Behavior"); // Track memory usage Runtime runtime = Runtime.getRuntime(); Thread allocator = new Thread(() -> { List<byte[]> objects = new ArrayList<>(); for (int i = 0; i < 1000; i++) { objects.add(new byte[1024 * 512]); // 512KB // Print memory info periodically if (i % 100 == 0) { printMemoryStats(runtime, i); } try { Thread.sleep(10); } catch (InterruptedException e) { break; } } }); allocator.start(); allocator.join(); } private static void printMemoryStats(Runtime runtime, int iteration) { long max = runtime.maxMemory(); long total = runtime.totalMemory(); long free = runtime.freeMemory(); long used = total - free; System.out.printf("Iteration %d: Used=%dMB, Free=%dMB, Total=%dMB, Max=%dMB%n", iteration, used / (1024 * 1024), free / (1024 * 1024), total / (1024 * 1024), max / (1024 * 1024)); } } 10. GC Tuning Examples
public class GCTuningExamples { // Common JVM flags for different scenarios: // 1. Throughput optimization // -XX:+UseParallelGC -XX:ParallelGCThreads=4 -Xmx2g -Xms2g // 2. Low latency optimization // -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx4g // 3. Very large heap with low pauses // -XX:+UseZGC -Xmx16g -Xms16g // 4. Small application // -XX:+UseSerialGC -Xmx512m public static void simulateWorkload(String workloadType) { switch (workloadType) { case "throughput": // Batch processing - favor throughput for (int i = 0; i < 1000000; i++) { processBatchItem(i); } break; case "low-latency": // Real-time processing - favor low pauses for (int i = 0; i < 1000; i++) { processRealTimeItem(i); try { Thread.sleep(1); } catch (InterruptedException e) {} } break; case "memory-intensive": // Large memory usage List<byte[]> largeData = new ArrayList<>(); for (int i = 0; i < 100; i++) { largeData.add(new byte[1024 * 1024 * 10]); // 10MB } break; } } private static void processBatchItem(int i) { // Create some short-lived objects byte[] data = new byte[1024]; // Process and discard } private static void processRealTimeItem(int i) { // Minimal object creation to avoid GC during processing // Reuse objects when possible } } 11. Memory Leak Detection
public class MemoryLeakDetection { private static final List<Object> LEAK = new ArrayList<>(); public static void main(String[] args) throws InterruptedException { System.out.println("Memory Leak Detection Demo"); // Simulate memory leak Thread leakyThread = new Thread(() -> { for (int i = 0; i < 100000; i++) { LEAK.add(new byte[1024 * 10]); // 10KB - never cleared try { Thread.sleep(1); } catch (InterruptedException e) { break; } } }); leakyThread.start(); Thread.sleep(5000); // Let it run // Force GC to see if memory is reclaimed System.gc(); Thread.sleep(1000); System.out.println("Leak list size: " + LEAK.size()); System.out.println("Memory not reclaimed due to static reference"); } } Key Points Summary
- Serial GC: Single-threaded, good for small apps
- Parallel GC: Multi-threaded, good for throughput
- CMS: Low pause, deprecated
- G1: Balanced, default since Java 9
- ZGC: Very low pause, large heaps
- Shenandoah: Low pause, concurrent compaction
Choose based on your application's requirements for throughput vs latency, and heap size considerations.