Heap & Stack

Java memory model — Heap vs Stack, what lives where, and how understanding this prevents OutOfMemoryErrors and StackOverflowErrors.

Last reviewed: January 2025

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: The JVM has two main memory areas: the Stack (per-thread, stores method calls and local variables, automatic cleanup) and the Heap (shared, stores all objects, managed by Garbage Collector). When you declare int x = 5, x is on the stack. When you do new Employee(), the Employee object is on the heap and a reference to it is on the stack.

🔍 What Is It?

Stack Memory:

  • →One stack per thread
  • →Stores: method call frames, local variables (primitives and object references), return addresses
  • →LIFO (Last In, First Out) — method frames pushed on call, popped on return
  • →Fixed size (default: 512KB–1MB per thread, configurable with -Xss)
  • →Automatic management — no GC needed, memory freed when method returns
  • →Fast: simple pointer increment/decrement
  • →StackOverflowError when too deep (usually infinite recursion)

Heap Memory:

  • →One heap shared by all threads
  • →Stores: all objects (new Foo()), arrays, String pool (part of heap)
  • →Managed by Garbage Collector
  • →Configurable size: -Xms (initial), -Xmx (maximum)
  • →Generational structure:

- Young Generation (Eden + Survivor spaces): new objects allocated here

- Old Generation (Tenured): long-lived objects promoted here

- Minor GC: cleans young generation (fast, frequent)

- Major/Full GC: cleans old generation (slow, rare)

Metaspace (Java 8+, was PermGen):

  • →Stores class metadata, method bytecode, static variables
  • →Native memory (not part of heap)
  • →Grows dynamically (no fixed size by default)

Code Cache:

  • →Stores JIT-compiled native code
  • →Fixed size, configurable with -XX:ReservedCodeCacheSize

🎯 Why Do We Need It?

Understanding Heap vs Stack is essential for:

  • →Diagnosing OutOfMemoryError — is it heap (too many objects) or stack (too deep recursion)?
  • →Understanding GC — only heap objects are garbage collected
  • →Memory leak analysis — objects on heap that are never GC'd
  • →Thread safety — stack variables are thread-private (safe); heap objects are shared (need synchronization)
  • →Performance tuning — large heaps need tuning; deep stacks need -Xss adjustment

🏗️ What Happens in Real Projects?

In production:

  • →OOM: Java heap space — increase -Xmx or find memory leak with heap dump
  • →OOM: Metaspace — classloader leak, increase -XX:MaxMetaspaceSize
  • →StackOverflowError — infinite recursion, increase -Xss or fix the recursion
  • →High GC pause — too many short-lived objects, tune young generation size

⚙️ How It Works

Object Allocation:

void processOrder(Long orderId) {
    // Stack: frame for processOrder
    // Stack: orderId reference (Long object is on heap)
    
    Order order = new Order(orderId);  // Order object on HEAP
    // Stack: 'order' variable (reference to heap object)
    
    int total = calculateTotal(order); // Stack: new frame for calculateTotal
    // Stack: 'total' int value
    
    // When processOrder returns:
    // Stack frame popped — 'order' reference gone
    // Order object on heap becomes eligible for GC (if no other references)
}

Escape Analysis (JIT optimization):

The JIT compiler can detect that some objects never "escape" the method — they are not returned, stored in fields, or passed to other threads. These objects may be allocated on the stack instead of the heap, eliminating GC pressure. This is transparent to developers but explains why Java's performance is better than naive analysis suggests.

💻 Example

Memory Allocation in Practice — How to diagnose and fix memory issues in production Java.

java
public class MemoryDemo {
    // Static field: in Metaspace (class-level, not per-instance)
    private static final String APP_NAME = "MyApp";
    
    // Instance field: on Heap (part of each MemoryDemo object)
    private String name;
    private int count;  // int value stored directly in the object on heap
    
    public void demonstrateMemory() {
        // Local primitive: on Stack
        int localInt = 42;
        
        // Local reference: on Stack; object it points to: on Heap
        String localString = new String("hello");
        
        // String literal: in String Pool (part of Heap)
        String pooled = "hello";  // may reuse existing pool entry
        
        // Array: on Heap (arrays are objects)
        int[] numbers = new int[100];  // array object on heap
        // numbers variable (reference) on stack
        
        // After this method returns:
        // localInt, localString (reference), numbers (reference) popped from stack
        // The String object and int[] array become GC-eligible (if no other refs)
        // APP_NAME stays in Metaspace forever (class-level)
    }
}

🤔 Common Confusions

❓ Are all objects always on the heap?

Mostly yes, but JIT escape analysis can allocate non-escaping objects on the stack. Also, Java 21+ with Valhalla (value types) will allow some objects to be stored inline without heap allocation. For practical purposes, assume new Object() allocates on the heap.

❓ What is the difference between OutOfMemoryError: Java heap space and OutOfMemoryError: Metaspace?

Heap space OOM: too many objects on the heap — increase -Xmx or fix memory leaks. Metaspace OOM: too many classes loaded — usually a classloader leak (common in app servers with hot deployment) or reflection-heavy frameworks. Increase -XX:MaxMetaspaceSize or fix the classloader leak.

❓ Why does StackOverflowError occur?

Each method call creates a new stack frame. StackOverflowError occurs when the stack has no more space for new frames. The most common cause is infinite recursion (a method calling itself without a base case). Fix: find the recursion and add a proper base case, or convert to iteration. Rarely, very deep legitimate recursion needs -Xss to increase stack size.

🚨 Common Production Issues

Memory leak — objects held in static collections (caches, listeners) that are never removed. Use weak references or bounded caches (Caffeine) for caches.

Large heap causing long GC pauses — with G1GC, heaps up to 32GB are manageable. For larger heaps, use ZGC or Shenandoah which have sub-millisecond pauses.

Thread stack memory — each thread uses -Xss memory for its stack. 1000 threads * 512KB = 500MB just for stacks. With virtual threads (Java 21), stack memory per thread is much smaller.

✅ Best Practices

Set -Xms equal to -Xmx in production to avoid heap resizing pauses.

Always set -XX:+HeapDumpOnOutOfMemoryError in production.

Use G1GC for most applications; ZGC for latency-sensitive large heaps.

Monitor GC activity with -Xlog:gc* and alert on high GC pause times.

Use weak references (WeakHashMap) for caches to allow GC to reclaim entries.

Profile memory usage before and after major releases to catch regressions.

🏛️ Architect's Note

Senior Architect's Perspective

Memory sizing is not a one-time decision. As your application grows (more users, more data, more features), heap requirements change. Establish memory monitoring from day one: track heap usage over time, GC pause frequency, and Metaspace size. Set alerts at 80% heap usage. A heap dump from production (taken during normal operation) analyzed in Eclipse MAT is the most effective way to understand your application's memory profile.

💬 Frequently Asked Questions

How do I analyze a heap dump?

Use Eclipse Memory Analyzer (MAT) or VisualVM. In MAT: (1) Open the .hprof file, (2) Run "Leak Suspects" report — MAT identifies objects that are large and not expected to be retained, (3) Look at "Dominator Tree" to see what objects hold the most memory, (4) Use "OQL" (Object Query Language) to query specific object types. Common finding: a static Map or List holding millions of objects that should have been released.

🎯 Key Takeaways

1Stack: per-thread, method frames and local variables, auto-managed, fast
2Heap: shared, all objects, GC-managed, configurable with -Xms/-Xmx
3Metaspace: class metadata, native memory, replaced PermGen in Java 8
4StackOverflowError = too deep recursion; OutOfMemoryError = heap/Metaspace full
5Always set -XX:+HeapDumpOnOutOfMemoryError in production
6Monitor GC with -Xlog:gc* and alert on high pause times

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages