Skip to content

Memory Management

Memory management involves allocating and freeing memory for programs. Key concepts: Stack (fixed-size, stores local variables and call frames, LIFO), Heap (dynamic, stores objects), Garbage Collection (automatic memory reclamation — mark & sweep, generational GC). In Java, GC handles heap cleanup automatically. Common issues: memory leaks, OutOfMemoryError, excessive GC pauses.


Stack vs Heap

The Stack is thread-private, stores primitives, local references, and method call frames — it's fast (just a pointer move) and auto-cleaned when methods return. The Heap is shared across threads, stores objects and arrays — it requires garbage collection. StackOverflowError means the stack is full (usually infinite recursion); OutOfMemoryError means the heap is full.

Deep Dive: Comparison and Example
Feature Stack Heap
Stores Primitives, references, method frames Objects, arrays
Speed Very fast (pointer moves) Slower (allocation + GC)
Size Small, fixed per thread (~512KB-1MB) Large, shared across threads
Lifecycle Auto-freed when method returns GC-managed
Error StackOverflowError OutOfMemoryError
Thread safety Thread-private Shared (needs sync)
public void example() {
    int x = 10;              // Stack — primitive value
    String name = "hello";   // Stack — reference;  Heap — String object
    User user = new User();  // Stack — reference;  Heap — User object
}  // x, name, user references removed from stack
   // String and User objects eligible for GC if no other references

Garbage Collection

Java's GC automatically reclaims memory occupied by unreachable objects. It uses mark and sweep: traverse from GC roots (stack refs, static fields, active threads), mark reachable objects, sweep unmarked ones. Java uses generational GC — most objects die young (collected in Young Gen with minor GC), long-lived objects promote to Old Gen (collected with major GC).

Deep Dive: Generational GC
┌──────────────────────────────────┐
│ Young Generation (Minor GC)      │
│  Eden → Survivor 0 → Survivor 1 │
│  (most objects die here)         │
├──────────────────────────────────┤
│ Old Generation (Major GC)        │
│  (long-lived objects)            │
└──────────────────────────────────┘

Object lifecycle:

  1. New object → Eden space
  2. Survives Minor GC → Survivor (S0 ↔ S1)
  3. Survives many GC cycles → promoted to Old Gen
  4. Old Gen full → Major GC (longer pause, stop-the-world)
Deep Dive: GC Roots

GC roots are the starting points for reachability analysis. An object is eligible for GC only if it's not reachable from any root:

  • Local variables on the stack of active threads
  • Active threads themselves
  • Static fields of loaded classes
  • JNI references from native code
Deep Dive: GC Algorithms in Java
Algorithm Description Use Case
Serial GC Single-threaded, stop-the-world Small apps, client VMs
Parallel GC Multi-threaded young gen collection Throughput-focused apps
G1 GC Region-based, targets pause time (default since Java 9) General purpose
ZGC Concurrent, ultra-low pause (<10ms) Latency-sensitive apps
Shenandoah Concurrent compaction Low-pause alternative to ZGC

Memory Leaks in Java

Despite GC, Java can have memory leaks — objects that are reachable but no longer needed. Common causes: static collections that grow forever, unclosed resources, inner classes holding outer references, forgotten listeners/callbacks. Detection: profiling tools (VisualVM, JProfiler, Eclipse MAT) + heap dumps.

Deep Dive: Common Memory Leak Causes
// 1. Static collections that grow forever
private static final List<Object> cache = new ArrayList<>();
public void process(Object data) {
    cache.add(data);  // Never cleared! Grows until OOM
}

// 2. Unclosed resources
public void readFile() {
    InputStream is = new FileInputStream("file");
    // Missing is.close() — use try-with-resources instead
}

// 3. Inner class holds reference to outer class
public class Outer {
    private byte[] largeData = new byte[10_000_000];  // 10MB

    public Runnable createTask() {
        return new Runnable() {
            public void run() {
                // This anonymous class holds implicit reference to Outer
                // Outer (and its 10MB) can't be GC'd while this Runnable lives
            }
        };
        // Fix: use a static inner class or lambda (lambdas don't capture 'this'
        // unless you reference it)
    }
}

// 4. Forgotten listeners/callbacks
eventBus.register(listener);
// Never called: eventBus.unregister(listener);

try-with-resources

try-with-resources (Java 7+) automatically closes resources that implement AutoCloseable when the block exits — even if an exception occurs. Replaces error-prone manual try-finally patterns. Multiple resources are closed in reverse declaration order.

Deep Dive: Examples
// Old way — error-prone, verbose
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("file.txt"));
    return reader.readLine();
} finally {
    if (reader != null) reader.close();  // What if close() throws?
}

// Modern way — try-with-resources
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    return reader.readLine();
}  // Automatically closed, even if exception occurs

// Multiple resources — closed in reverse order
try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    // All automatically closed in reverse order: rs → ps → conn
}

Resource must implement AutoCloseable (or Closeable).


OutOfMemoryError Scenarios

Common OOM scenarios: Java heap space (heap full — increase -Xmx or fix leak), Metaspace (too many classes loaded), GC overhead limit exceeded (GC spending >98% time for <2% recovery — likely a leak). StackOverflowError is a separate error caused by infinite recursion or very deep call stacks.

Deep Dive: OOM Types and Fixes
java.lang.OutOfMemoryError: Java heap space
→ Heap full. Increase -Xmx or fix memory leak.

java.lang.OutOfMemoryError: Metaspace
→ Too many classes loaded (classloader leak). Increase -XX:MaxMetaspaceSize.

java.lang.OutOfMemoryError: GC overhead limit exceeded
→ GC spending >98% time collecting <2% memory. Almost certainly a leak.

java.lang.StackOverflowError
→ Infinite recursion or very deep call stack. Increase -Xss or fix recursion.
Deep Dive: Debugging Memory Issues
# Generate heap dump on OOM
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof -jar app.jar

# Generate heap dump on demand
jmap -dump:format=b,file=heap.hprof <pid>

# Monitor GC activity
java -Xlog:gc* -jar app.jar

# Useful JVM flags
-Xms512m        # Initial heap size
-Xmx2g          # Maximum heap size
-XX:+UseG1GC    # Use G1 garbage collector

Tools: VisualVM, Eclipse MAT (Memory Analyzer), JProfiler, jcmd, jstat


Common Interview Questions

Common Interview Questions
  • What is the difference between stack and heap?
  • How does garbage collection work in Java?
  • What is generational GC? Why does it work?
  • What causes a memory leak in Java? Give examples.
  • How do you detect and fix memory leaks?
  • What is the difference between OutOfMemoryError and StackOverflowError?
  • What is try-with-resources?
  • What are GC roots?
  • What JVM flags would you use to diagnose memory issues?
  • What is the difference between Minor GC and Major GC?
  • Compare G1 GC and ZGC.