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:
- New object → Eden space
- Survives Minor GC → Survivor (S0 ↔ S1)
- Survives many GC cycles → promoted to Old Gen
- 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
OutOfMemoryErrorandStackOverflowError? - 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.