Java Garbage Collection Guide: Understanding and Tuning

1️⃣ Introduction

Understanding and tuning garbage collection is crucial for optimal Java application performance. This guide covers GC algorithms, monitoring tools, and tuning strategies.

Key areas covered:

  • GC Algorithms and Collectors
  • Memory Management Basics
  • GC Tuning Strategies
  • Monitoring and Analysis
  • Common GC Issues
  • Performance Optimization
  • Best Practices
  • Troubleshooting

2️⃣ GC Algorithms

🔹 Serial GC

# Serial GC Configuration
-XX:+UseSerialGC
-Xms2g
-Xmx2g

# Memory Generations
Young Generation:
  - Eden Space
  - Survivor Space 0
  - Survivor Space 1
Old Generation:
  - Tenured Space

# GC Process
1. Mark live objects
2. Copy surviving objects
3. Compact heap space

🔹 Parallel GC

# Parallel GC Configuration
-XX:+UseParallelGC
-XX:ParallelGCThreads=4
-XX:MaxGCPauseMillis=200
-XX:GCTimeRatio=99

# Advanced Tuning
-XX:YoungGenerationSizeIncrement=20
-XX:TenuredGenerationSizeIncrement=20
-XX:AdaptiveSizeDecrementScaleFactor=4

# Monitoring
jstat -gcutil $pid 1000
jstat -gccause $pid 1000

3️⃣ G1 Garbage Collector

🔹 Basic Configuration

# G1 GC Configuration
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16M
-XX:G1ReservePercent=10
-XX:InitiatingHeapOccupancyPercent=45

# Logging Configuration
-Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=100m

# Advanced Settings
-XX:ConcGCThreads=4
-XX:ParallelGCThreads=8
-XX:G1MixedGCLiveThresholdPercent=90
-XX:G1HeapWastePercent=5

🔹 Region Management

public class G1GCDemo {
    // Simulate object allocation patterns
    public void allocateObjects() {
        List objects = new ArrayList<>();
        Random random = new Random();
        
        while (true) {
            // Allocate objects of varying sizes
            int size = random.nextInt(1024 * 1024);
            objects.add(new byte[size]);
            
            // Remove some objects to trigger GC
            if (objects.size() > 100) {
                objects.subList(0, 50).clear();
            }
            
            // Sleep to control allocation rate
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
    
    public static void main(String[] args) {
        // JVM arguments for monitoring
        // -XX:+UseG1GC
        // -XX:+PrintGCDetails
        // -XX:+PrintGCTimeStamps
        // -Xloggc:gc.log
        new G1GCDemo().allocateObjects();
    }
}

4️⃣ Memory Management

🔹 Heap Structure

// Memory allocation example
public class MemoryManagement {
    // Large object allocation
    private static final int LARGE_OBJECT_SIZE = 1024 * 1024;
    
    public void demonstrateAllocation() {
        // Young generation allocation
        List youngObjects = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            youngObjects.add(new byte[1024]);
        }
        
        // Old generation promotion
        List oldObjects = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            oldObjects.add(new byte[LARGE_OBJECT_SIZE]);
            System.gc(); // Force GC for demonstration
        }
    }
    
    // Memory leak prevention
    public class ResourceManager implements AutoCloseable {
        private final List resources = new ArrayList<>();
        
        public void allocateResource() {
            resources.add(new byte[1024]);
        }
        
        @Override
        public void close() {
            resources.clear();
        }
    }
    
    public void properResourceManagement() {
        try (ResourceManager manager = new ResourceManager()) {
            manager.allocateResource();
            // Use resources
        }
    }
}

🔹 Memory Leaks

public class MemoryLeakExamples {
    // Common memory leak: Static collections
    private static final List staticList = new ArrayList<>();
    
    // Memory leak through event listeners
    public class EventManager {
        private final List listeners = new ArrayList<>();
        
        public void addEventListener(EventListener listener) {
            listeners.add(listener);
        }
        
        // Missing removeEventListener method!
    }
    
    // Proper implementation with weak references
    public class SafeEventManager {
        private final List> listeners = 
            new ArrayList<>();
        
        public void addEventListener(EventListener listener) {
            listeners.add(new WeakReference<>(listener));
        }
        
        public void removeEventListener(EventListener listener) {
            listeners.removeIf(ref -> {
                EventListener l = ref.get();
                return l == null || l.equals(listener);
            });
        }
        
        public void fireEvent() {
            listeners.removeIf(ref -> ref.get() == null);
            for (WeakReference ref : listeners) {
                EventListener listener = ref.get();
                if (listener != null) {
                    listener.onEvent();
                }
            }
        }
    }
}



5️⃣ GC Tuning

🔹 Performance Tuning

# Common GC Tuning Scenarios

# High-Throughput Configuration
-XX:+UseParallelGC
-XX:ParallelGCThreads=8
-XX:MaxGCPauseMillis=500
-XX:GCTimeRatio=19
-Xms4g
-Xmx4g

# Low-Latency Configuration
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50
-XX:G1HeapRegionSize=16M
-XX:+ParallelRefProcEnabled
-XX:G1RSetUpdatingPauseTimePercent=5
-Xms8g
-Xmx8g

# Container-Optimized Configuration
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
-XX:InitialRAMPercentage=50.0
-XX:+UseG1GC
-XX:+ExitOnOutOfMemoryError

🔹 Monitoring

public class GCMonitoring {
    private static final Logger logger = 
        LoggerFactory.getLogger(GCMonitoring.class);
    
    public void monitorGC() {
        List gcBeans = 
            ManagementFactory.getGarbageCollectorMXBeans();
            
        for (GarbageCollectorMXBean gcBean : gcBeans) {
            logger.info("GC Name: {}", gcBean.getName());
            logger.info("Collection Count: {}", 
                gcBean.getCollectionCount());
            logger.info("Collection Time: {} ms", 
                gcBean.getCollectionTime());
        }
    }
    
    public void monitorMemory() {
        MemoryMXBean memoryBean = 
            ManagementFactory.getMemoryMXBean();
        MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
        
        logger.info("Heap Memory Usage:");
        logger.info("Init: {} MB", 
            heapUsage.getInit() / (1024 * 1024));
        logger.info("Used: {} MB", 
            heapUsage.getUsed() / (1024 * 1024));
        logger.info("Committed: {} MB", 
            heapUsage.getCommitted() / (1024 * 1024));
        logger.info("Max: {} MB", 
            heapUsage.getMax() / (1024 * 1024));
    }
}

6️⃣ Troubleshooting

🔹 Common Issues

# OutOfMemoryError Analysis
jmap -dump:format=b,file=heap.hprof $pid

# Thread Dump Analysis
jstack -l $pid > thread_dump.txt

# GC Log Analysis
# Enable GC logging
-Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=100m

# Heap Analysis
jmap -heap $pid

# Class Histogram
jmap -histo $pid

🔹 Performance Analysis

public class PerformanceAnalysis {
    private static final Logger logger = 
        LoggerFactory.getLogger(PerformanceAnalysis.class);
    
    public void analyzeGCImpact() {
        long startTime = System.nanoTime();
        long totalGCTime = 0;
        
        for (GarbageCollectorMXBean gc : 
                ManagementFactory.getGarbageCollectorMXBeans()) {
            totalGCTime += gc.getCollectionTime();
        }
        
        long uptime = 
            ManagementFactory.getRuntimeMXBean().getUptime();
        double gcTimePercentage = 
            (double) totalGCTime / uptime * 100;
        
        logger.info("GC Time Percentage: {}%", 
            String.format("%.2f", gcTimePercentage));
    }
    
    public void analyzeMemoryUsage() {
        MemoryMXBean memoryBean = 
            ManagementFactory.getMemoryMXBean();
        List memoryPools = 
            ManagementFactory.getMemoryPoolMXBeans();
        
        for (MemoryPoolMXBean pool : memoryPools) {
            MemoryUsage usage = pool.getUsage();
            logger.info("Memory Pool: {}", pool.getName());
            logger.info("Used: {} MB", 
                usage.getUsed() / (1024 * 1024));
            logger.info("Max: {} MB", 
                usage.getMax() / (1024 * 1024));
        }
    }
}

7️⃣ Q&A / Frequently Asked Questions

Choose based on your requirements: (1) G1GC for balanced performance. (2) Parallel GC for throughput. (3) ZGC for low latency. (4) Serial GC for small applications. Consider factors like heap size, pause time requirements, and CPU resources.

To diagnose memory leaks: (1) Use heap dump analysis. (2) Monitor memory usage trends. (3) Use profiling tools. (4) Analyze GC logs. (5) Check for static collections. (6) Review resource cleanup. (7) Monitor classloader leaks. (8) Use memory analyzers. (9) Check thread states. (10) Review connection pools.

GC optimization strategies: (1) Right-size heap generations. (2) Choose appropriate GC algorithm. (3) Tune GC parameters. (4) Monitor GC frequency. (5) Optimize object lifecycle. (6) Use proper data structures. (7) Implement caching strategies. (8) Regular monitoring. (9) Profile application. (10) Review allocation patterns.

8️⃣ Best Practices & Pro Tips 🚀

  • Monitor GC performance regularly
  • Choose appropriate GC algorithm
  • Size generations properly
  • Implement proper memory management
  • Use GC logging
  • Analyze GC logs
  • Profile memory usage
  • Optimize object lifecycle
  • Regular performance testing
  • Document GC configuration
  • Monitor memory leaks
  • Implement proper error handling

Read Next 📖

Conclusion

Understanding and properly tuning garbage collection is crucial for Java application performance. By following the guidelines and best practices in this guide, you can effectively manage memory and optimize GC behavior for your specific use case.

Remember to regularly monitor GC performance, analyze patterns, and adjust configurations based on your application's requirements and behavior.