Garbage Collection

Automatic Memory Management in Java

← Back to Index

What is Garbage Collection?

Imagine you're at a restaurant eating. After you finish, you don't wash your own dishes - the staff cleans up for you! Garbage Collection (GC) is like that restaurant staff for your program's memory - it automatically cleans up objects you're no longer using.

Real-World Analogy: Office Cleaning Service

Manual Memory Management (C/C++) vs Automatic (Java)

// C/C++ - Manual (you must remember to free memory!)
int* ptr = (int*)malloc(sizeof(int));
*ptr = 42;
// ... use ptr ...
free(ptr);  // Must manually free! If you forget = memory leak!

// Java - Automatic (GC handles it!)
Person p = new Person("Alice");
// ... use p ...
p = null;  // Object becomes eligible for GC
// GC automatically cleans it up - you don't worry about it!

Why Garbage Collection Matters

How Does GC Know What to Clean?

Reachability - The Key Concept

An object is reachable if you can access it through references. If unreachable, it's garbage!

public static void main(String[] args) {
    Person p1 = new Person("Alice");  // Reachable via p1
    Person p2 = new Person("Bob");    // Reachable via p2

    p1 = null;  // Alice object is now UNREACHABLE → Garbage!

    p2 = new Person("Charlie");  // Bob becomes UNREACHABLE → Garbage!
}

Visualization:
┌─────────┐     ┌──────────────┐
│   p1    │────→│ Person:Alice │  Reachable ✓
└─────────┘     └──────────────┘

After p1 = null:
┌─────────┐     ┌──────────────┐
│   p1    │ null │ Person:Alice │  Unreachable ✗ → GARBAGE
└─────────┘     └──────────────┘

GC Roots - Starting Points

GC starts from "roots" and traces all reachable objects. Roots include:

GC Root Tracing:
Start → Stack (local vars) → Objects they point to → Objects those point to...
Everything not traced = Garbage!

Generational Garbage Collection

Java's GC uses a clever insight: Most objects die young!

The Weak Generational Hypothesis

Observation:
• 90% of objects die within seconds/minutes of creation
• 10% survive long-term

Solution:
Split heap into generations, clean young objects frequently!

Heap Generations

┌────────────────────────────────────────────────┐
│              JAVA HEAP                         │
├────────────────────────────────────────────────┤
│  YOUNG GENERATION (33%)                        │
│  ┌──────────┬──────────────┬──────────────┐   │
│  │  Eden    │ Survivor S0  │ Survivor S1  │   │
│  │  (90%)   │    (5%)      │    (5%)      │   │
│  │          │              │              │   │
│  │ New objs │ Survived 1+  │ Survived 2+  │   │
│  │ born here│  GC cycles   │  GC cycles   │   │
│  └──────────┴──────────────┴──────────────┘   │
├────────────────────────────────────────────────┤
│  OLD GENERATION (Tenured) (67%)                │
│  Long-lived objects that survived many GCs     │
│  Cleaned less frequently                       │
└────────────────────────────────────────────────┘

Object Lifecycle

Step 1: Object is born
Person p = new Person();
→ Created in Eden space

Step 2: Minor GC happens (Eden fills up)
→ Dead objects: Cleaned up
→ Surviving objects: Moved to Survivor S0, age = 1

Step 3: Next Minor GC
→ Eden + S0 objects examined
→ Survivors moved to S1, age incremented (age = 2)

Step 4: Objects keep bouncing between S0 ↔ S1
→ Age increments each time

Step 5: Object reaches age threshold (default: 15)
→ Promoted to Old Generation (Tenured)

Step 6: Lives in Old Gen until Major GC
→ Eventually cleaned if unreachable

Types of Garbage Collection

1. Minor GC (Young Generation)

2. Major GC / Full GC (Whole Heap)

Stop-The-World Events

During GC, application threads must pause (Stop-The-World). Modern GCs minimize this!

Application Timeline:
App running... → [GC Pause 5ms] → App running... → [GC Pause 3ms] → ...

During GC pause:
✗ Application threads stopped
✓ GC threads working
Goal: Minimize pause time!

Garbage Collection Algorithms

1. Serial GC (Default for small apps)

// Enable with:
java -XX:+UseSerialGC MyApp

How it works:
• Single-threaded GC
• Stops all app threads
• Simple, low overhead
• Good for: Small apps, single-core machines

2. Parallel GC (Throughput Collector)

// Enable with:
java -XX:+UseParallelGC MyApp

How it works:
• Multi-threaded GC
• Uses multiple cores for GC
• Faster cleanup
• Good for: Batch processing, high throughput apps

3. G1 GC (Garbage First - Default Java 9+)

// Enable with (or default in Java 9+):
java -XX:+UseG1GC MyApp

How it works:
• Divides heap into regions
• Collects regions with most garbage first
• Predictable pause times
• Good for: Large heaps, low-latency apps

4. ZGC (Scalable Low-Latency GC - Java 11+)

// Enable with:
java -XX:+UseZGC MyApp

How it works:
• Ultra-low pause times (<10ms)
• Concurrent GC (runs alongside app)
• Handles huge heaps (terabytes)
• Good for: Ultra-low-latency requirements

Memory Leaks in Java - Yes, They Can Happen!

Even with GC, you can leak memory if you hold references to objects you no longer need.

Common Memory Leak Scenarios

1. Static Collections

❌ Memory Leak Example
public class Cache {
    private static List<Object> cache = new ArrayList<>();

    public static void add(Object obj) {
        cache.add(obj);  // Objects NEVER removed!
    }
}

// Every object added stays in memory forever!
// Static collection is always reachable → Objects never GC'd

2. Unclosed Resources

❌ Resource Leak
FileInputStream fis = new FileInputStream("file.txt");
// ... use fis ...
// Never closed! File handle leaked!
✅ Solution: Try-With-Resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // use fis...
}  // Automatically closed!

3. Event Listeners Not Removed

// BAD: Add listener but never remove
button.addActionListener(myListener);
// If button outlives listener, listener stays in memory!

// GOOD: Remove when done
button.removeActionListener(myListener);

Monitoring and Tuning GC

GC Logging

// Enable GC logging
java -Xlog:gc* -Xlog:gc:gc.log MyApp

// Output example:
[0.123s][info][gc] GC(0) Pause Young (Normal) 12M->2M(50M) 4.567ms
//               │                       │      │     │
//               Type                    Before After Time

Heap Size Tuning

// Set initial heap size (Xms) and max heap size (Xmx)
java -Xms512m -Xmx2g MyApp

// -Xms512m: Start with 512MB heap
// -Xmx2g: Allow heap to grow up to 2GB

Best Practice: Set Xms = Xmx (avoid resizing overhead)
java -Xms2g -Xmx2g MyApp

Monitoring Tools

// View GC stats in real-time
jstat -gc 12345 1000
//        │     │
//        PID   Every 1000ms

Best Practices

GC Best Practices
  • Avoid creating unnecessary objects: Reuse when possible
  • Use object pools for expensive objects: Database connections, threads
  • Clear large collections when done: list.clear()
  • Use weak references for caches: WeakHashMap, SoftReference
  • Close resources: Try-with-resources or finally blocks
  • Avoid finalizers: They delay GC and are unpredictable
  • Profile before tuning: Measure, don't guess
  • Don't call System.gc(): Let JVM decide
  • Don't over-tune: Default settings work for most apps

Writing GC-Friendly Code

// BAD: Creates many temporary objects
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i;  // Creates 1000 String objects!
}

// GOOD: Reuses single StringBuilder
StringBuilder result = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    result.append(i);  // Single object!
}

Why It Matters

Remember

Garbage Collection is like a self-cleaning oven - it handles the dirty work so you can focus on cooking (coding)! Trust it, but understand how it works for when you need to tune performance.