Skip to content

Garbage Collection

Java Memory Management

1. Java Memory Architecture [Understand JVM memory structure]

Section titled “1. Java Memory Architecture [Understand JVM memory structure]”

JVM memory is divided into Heap (object storage) and Non-Heap (metadata, code). Understanding this is fundamental for memory optimization.

graph TB
    subgraph "JVM Memory"
        Heap[Heap - Object Storage]
        NonHeap[Non-Heap Memory]
        Native[Native Memory]

        subgraph Heap
            Young[Young Generation]
            Old[Old Generation]
        end

        subgraph NonHeap
            Meta[Metaspace - Class Metadata]
            Code[Code Cache - Compiled Code]
        end

        subgraph ThreadSpecific
            JVMStack[JVM Stack - Method calls]
            PC[Program Counter - Current instruction]
            NativeStack[Native Method Stack]
        end
    end

2. Stack Memory in Detail [Know thread-specific memory]

Section titled “2. Stack Memory in Detail [Know thread-specific memory]”

Stack Memory is per-thread memory for method execution:

  • Stores primitive values and object references (not objects themselves)
  • Each method call creates a new stack frame
  • Follows LIFO (Last-In-First-Out) principle
  • Memory allocated/deallocated automatically

Stack Frame Contains:

  1. Local Variables
  2. Operand Stack (intermediate calculations)
  3. Frame Data (return address, exception table)

Example:

public void calculate() {
int x = 10; // Primitive - stored in stack
String str = "Hello"; // Reference in stack, object in heap
Object obj = new Object(); // Reference in stack, object in heap
}

3. Heap Memory Components [Know where objects live]

Section titled “3. Heap Memory Components [Know where objects live]”

Eden: New objects born here
Survivor Spaces (S0, S1): Surviving objects move between these
Old Generation: Long-lived objects promoted here
Metaspace: Replaced PermGen in Java 8, stores class metadata

4. Stack vs Heap Comparison [Critical differentiation]

Section titled “4. Stack vs Heap Comparison [Critical differentiation]”
AspectStack MemoryHeap Memory
ScopeThread-specificShared across threads
SizeFixed (small)Dynamic (large)
SpeedVery fastSlower
AllocationAutomatic (compile-time)Manual (runtime)
DeallocationAutomatic (scope ends)Via Garbage Collection
ContentsPrimitives, referencesObjects, arrays
Thread SafetyYes (per thread)No (requires synchronization
MemoryLimitedLarge (configurable)
Exampleint x = 10;new Object();

5. Garbage Collection Fundamentals [Understand how GC works]

Section titled “5. Garbage Collection Fundamentals [Understand how GC works]”

GC identifies dead objects using GC Roots (active references). Process: Mark → Sweep → Compact (optional).

GC Roots include:

  • Local variables (in stack)
  • Active threads
  • Static variables
  • JNI references

6. Minor GC vs Major GC [Differentiate collection types]

Section titled “6. Minor GC vs Major GC [Differentiate collection types]”
AspectMinor GCMajor GC (Full GC)
ScopeYoung Generation onlyEntire Heap
SpeedFast (ms)Slow (seconds+)
TriggerEden fullOld Gen full / System.gc()
PauseShortLong (Stop-the-World)
FrequencyHighLow

7. GC Algorithms Comparison [Know different collectors]

Section titled “7. GC Algorithms Comparison [Know different collectors]”
CollectorTypeBest ForKey Feature
SerialSingle-threadSmall appsSimple
ParallelMulti-threadThroughputMultiple GC threads
CMSConcurrentLow latencyMinimizes pauses
G1Region-basedBalancedPredictable pauses
ZGCConcurrentLarge heaps<10ms pauses
ShenandoahConcurrentResponsive appsConcurrent compaction

8. Reference Types [Memory management control]

Section titled “8. Reference Types [Memory management control]”
classDiagram
    class Reference~T~ {
        +T get()
        +void clear()
    }

    class StrongReference {
        "Prevents GC"
    }

    class SoftReference {
        "GC'd when memory low"
        "Good for cache"
    }

    class WeakReference {
        "GC'd when no strong refs"
        "WeakHashMap"
    }

    class PhantomReference {
        "Finalization tracking"
        "Cleanup operations"
    }

    Reference <|-- SoftReference
    Reference <|-- WeakReference
    Reference <|-- PhantomReference
    StrongReference --|> Object

1. Strong Reference (default):

Object obj = new Object(); // Only removed when obj = null

2. Soft Reference (memory-sensitive cache):

SoftReference<Object> softRef = new SoftReference<>(obj);
// GC clears only under memory pressure

3. Weak Reference (temporary mappings):

WeakReference<Object> weakRef = new WeakReference<>(obj);
// GC clears when no strong references remain

4. Phantom Reference (cleanup tasks):

PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
// Used for post-mortem cleanup operations
// 1. Use local scope for better stack usage
public void process() {
List<String> tempList = new ArrayList<>(); // Good: Stack reference
// ... use tempList
} // Stack frame cleared automatically
// 2. Reuse objects when possible
private static final SimpleDateFormat formatter =
new SimpleDateFormat("yyyy-MM-dd");
// 3. Use StringBuilder for string concatenation in loops
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item);
}
// 1. Memory leak - static collections
private static final List<Object> CACHE = new ArrayList<>();
public void addToCache(Object obj) {
CACHE.add(obj); // Objects never GC'd
}
// 2. Unnecessary object creation in loops
for (int i = 0; i < 1000; i++) {
String s = new String("constant"); // Creates 1000 objects!
// Use: String s = "constant";
}
// 3. Premature optimization
ObjectPool pool = new ObjectPool(); // Unless proven needed

10. Common JVM Flags [Must know for interviews]

Section titled “10. Common JVM Flags [Must know for interviews]”
Terminal window
# Heap Configuration
-Xms512m # Initial heap size
-Xmx2g # Maximum heap size
-Xss1m # Thread stack size
-XX:MetaspaceSize=256m # Metaspace initial size
# GC Selection
-XX:+UseG1GC # Use G1 collector
-XX:+UseParallelGC # Use Parallel collector
-XX:+UseZGC # Use ZGC (Java 11+)
# GC Tuning
-XX:NewRatio=2 # Old:Young = 2:1
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1
-XX:MaxGCPauseMillis=200 # Target max pause
# Monitoring
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./heapdump.hprof
-Xlog:gc*:file=gc.log

Q1: What’s the difference between Stack and Heap memory?

Section titled “Q1: What’s the difference between Stack and Heap memory?”

Stack stores method calls and local primitives/references (fast, thread-specific, limited). Heap stores objects (shared, larger, GC-managed). Stack variables exist only during method execution, while heap objects persist until GC’d.

Q2: What is Garbage Collection and how does it work?

Section titled “Q2: What is Garbage Collection and how does it work?”

GC automatically reclaims memory by identifying unreachable objects. Process: 1) Mark reachable objects from GC Roots, 2) Sweep unreachable objects, 3) Compact to reduce fragmentation.

Minor GC cleans Young Generation (Eden + Survivor). Fast, frequent. Major GC (Full GC) cleans entire heap. Slow, causes “stop-the-world” pause, should be minimized.

Starting points for reachability analysis:

  • Active thread stacks (local variables)
  • Static variables
  • JNI references
  • Monitor/synchronized objects

Q5: What is the difference between SoftReference and WeakReference?

Section titled “Q5: What is the difference between SoftReference and WeakReference?”

SoftReferences are cleared when JVM needs memory (good for cache). WeakReferences are cleared immediately when no strong references remain (good for temporary mappings).

Q6: When does an object become eligible for GC?

Section titled “Q6: When does an object become eligible for GC?”

When no references point to it (unreachable) AND it’s not a GC Root. Circular references without external references are also eligible.

PermGen (≤Java7) stored class metadata. Replaced by Metaspace (≥Java8) which uses native memory and auto-expands. OutOfMemoryError: PermGen space → OutOfMemoryError: Metaspace.

  1. Close resources (try-with-resources),
  2. Avoid static collections holding objects,
  3. Use WeakReference for listeners/caches, 4) Nullify references after use.

When GC stops application threads to safely identify live objects. Different collectors minimize this: CMS (concurrent), G1 (predictable pauses), ZGC (<10ms pauses).

Thread-Local Allocation Buffer. Each thread gets a private Eden region to allocate objects without synchronization, improving performance.

A remembered set that tracks Old Generation references pointing to Young Generation objects. Optimizes Minor GC by avoiding full heap scans.

Suggests JVM to run GC. Not guaranteed! Use Runtime.getRuntime().gc() as alternative. Generally avoid - interferes with JVM’s GC strategy.

JVM optimization that identifies objects not escaping method scope, allowing stack allocation instead of heap allocation.

A:

  • Throughput: Parallel GC
  • Low latency: G1, ZGC, Shenandoah
  • Small apps: Serial GC
  • Large heaps: G1 or ZGC

When free memory is scattered in small chunks. Solved by compaction (moving objects together). CMS suffers from fragmentation, G1/ZGC handle it better.

  1. Memory allocated in Eden, 2) Header initialized (hashcode, GC age), 3) Fields initialized, 4) Constructor executed, 5) Reference returned.

Q17: What is the young generation age threshold?

Section titled “Q17: What is the young generation age threshold?”

Objects surviving multiple Minor GCs get promoted to Old Generation. Default threshold is 15 (max 4-bit counter). Tuned via -XX:MaxTenuringThreshold.

Tools: jconsole, jvisualvm, jstat, jmap. Commands: jstat -gc <pid>, jmap -heap <pid>, jcmd <pid> VM.info.

Q19: What is the difference between Shallow and Deep heap size?

Section titled “Q19: What is the difference between Shallow and Deep heap size?”

Shallow heap - memory of object itself. Deep heap - object + all referenced objects. Important for memory leak analysis.

Q20: How does finalize() method affect GC?

Section titled “Q20: How does finalize() method affect GC?”

Objects with finalize() go to finalization queue, requiring extra GC cycle. Deprecated in Java 9. Use Cleaner/PhantomReference instead.

// Symptom: Increasing heap usage over time
// Solution: Use heap dump analysis
jmap -dump:live,format=b,file=heap.bin <pid>
// Analyze with Eclipse MAT or VisualVM
Terminal window
# Symptom: Frequent pauses, low throughput
# Solution: Increase heap or adjust generation sizes
-Xmx4g -Xms4g # Larger heap
-XX:NewRatio=1 # More space for young generation
Terminal window
# Symptom: Application freezes for seconds
# Solution: Switch to low-pause collector
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
# Or for Java 11+:
-XX:+UseZGC
  • Stack: Methods, local variables (primitives/references)
  • Heap: Objects, arrays
  • Metaspace: Class metadata, static variables
  • Code Cache: Compiled native code
  • Minor GC: Young generation only, fast
  • Major GC: Full heap, slow, avoid
  • Concurrent GC: Runs with application (CMS, G1)
  • Strong: Default, prevents GC
  • Soft: Cache, cleared under memory pressure
  • Weak: Temporary associations, immediate GC
  • Phantom: Cleanup operations, finalization
Terminal window
jps # List Java processes
jstat -gc <pid> 1000 # Monitor GC every second
jmap -histo:live <pid> # Heap histogram
jstack <pid> # Thread dump
  1. ZGC Production Ready (Java 15+): Sub-10ms pauses, TB heaps
  2. Shenandoah in OpenJDK: Low pause concurrent collector
  3. Epsilon GC: No-op collector for performance testing
  4. Deprecated Finalization (Java 9+): Use Cleaner API
  1. Be Practical: Relate concepts to real projects
  2. Use Examples: “In my project, we used G1 GC because…”
  3. Know Trade-offs: Every design choice has pros/cons
  4. Monitor First: Always emphasize profiling before tuning
  5. Stay Updated: Know recent Java version changes

Remember: Understanding Java memory management isn’t just about theory - it’s about writing efficient, scalable applications and troubleshooting production issues effectively.

import java.io.*;
public class OldWay {
public static void main(String[] args) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("test.txt"));
System.out.println(reader.readLine());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) reader.close(); // Manual cleanup
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
import java.io.*;
public class NewWay {
public static void main(String[] args) {
// Resource auto-closes after try block
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
e.printStackTrace();
}
// No finally needed - reader automatically closed!
}
}

Traditional: finally { if (reader != null) reader.close(); } ← Manual cleanup
Modern: try (BufferedReader reader = ...) ← Auto cleanup

// Old way - verbose
BufferedReader br = null;
try { br = new BufferedReader(...); }
finally { if (br != null) br.close(); }
// New way - clean
try (BufferedReader br = new BufferedReader(...)) {
// use br
}
// br automatically closed

Try-with-resources is cleaner, safer, and eliminates resource leak bugs!