Skip to content

Stack vs Heap

Understanding the Java memory model, particularly the distinction between stack and heap memory, is crucial for senior Java engineers. This knowledge impacts application performance, memory management, and troubleshooting.

The Java Virtual Machine (JVM) divides memory into several areas, with the two primary regions being:

  1. Stack Memory
  2. Heap Memory

Other memory areas include Method Area (Metaspace in modern JVMs), Runtime Constant Pool, Native Method Stack, and Code Cache.

  • Thread-specific: Each thread has its own stack
  • LIFO (Last-In-First-Out) structure
  • Fixed size: Determined by -Xss JVM parameter (default varies by JVM implementation, typically 512KB-1MB)
  • Automatically managed: Memory allocation/deallocation happens automatically as methods are called/returned
  • Fast access: Due to its simple allocation model
  • Temporary storage: Exists only during thread execution
  1. Primitive values: int, long, float, double, boolean, char, byte, short
  2. Object references: Pointers to objects stored in the heap
  3. Method frames: For each method invocation, containing:
    • Local variables
    • Partial results
    • Method return values
    • Method parameters
public void calculateSum() {
int a = 10; // Primitive stored on stack
int b = 20; // Primitive stored on stack
int sum = a + b; // Primitive stored on stack
Integer boxedSum = sum; // Reference stored on stack, actual Integer object on heap
doSomething(sum); // New frame pushed onto stack with 'sum' as parameter
} // When method exits, its frame is popped off the stack

Occurs when a thread’s stack size exceeds its allocated limit, commonly due to:

  • Excessive recursion without proper termination conditions
  • Very deep method call hierarchies
  • Large local variable declarations
// Stack overflow example
public void recursiveMethod() {
int[] largeArray = new int[10000]; // Large local variable
recursiveMethod(); // No termination condition
}
  • Shared across threads: All threads share the same heap
  • Dynamic size: Can grow/shrink during application execution
  • Configurable size: Set via -Xms (initial) and -Xmx (maximum) JVM parameters
  • Garbage collected: Memory management handled by the JVM’s garbage collector
  • Slower access: Compared to stack memory due to complexity
  • Persistent storage: Objects exist until no longer referenced
  1. Objects: All class instances and arrays
  2. Instance variables: Fields declared in classes
  3. Static variables: Class variables shared across instances
  4. String pool: Interned strings (in modern JVMs)

The heap is divided into generations for efficient garbage collection:

  1. Young Generation:

    • Eden Space: Initial allocation area
    • Survivor Spaces (S0, S1): For objects that survive garbage collection
  2. Old Generation (Tenured):

    • For long-lived objects promoted from young generation
  3. Metaspace (replaced PermGen in Java 8+):

    • Class metadata, method data, etc.
public void createObjects() {
// Object allocation on heap
Person person = new Person("John", 30); // 'person' reference on stack, Person object on heap
// Array allocation on heap
int[] numbers = new int[1000]; // 'numbers' reference on stack, array on heap
// String object on heap (may be in String pool)
String name = "Java Expert"; // 'name' reference on stack, String object on heap
}

Occurs when the JVM cannot allocate an object due to insufficient heap space:

// Potential heap OutOfMemoryError
List<byte[]> memoryConsumer = new ArrayList<>();
while (true) {
memoryConsumer.add(new byte[1024 * 1024]); // Continuously allocate 1MB arrays
}
CharacteristicStackHeap
Memory managementAutomatic (LIFO)Garbage collected
LifetimeShort-lived (method scope)Until no longer referenced
Thread safetyThread-specific (safe)Shared (requires synchronization)
SizeLimited, fixed per threadLarge, dynamic
Allocation/deallocation speedVery fastSlower
FragmentationRareCommon
Memory overheadLowHigher due to GC metadata
Access speedFasterSlower
Common errorsStackOverflowErrorOutOfMemoryError
public void demonstrateReferences() {
// Stack: primitive 'count' and reference 'items'
int count = 10;
List<String> items = new ArrayList<>(); // 'items' on stack, ArrayList on heap
// Adding elements to the list (objects stored on heap)
for (int i = 0; i < count; i++) { // 'i' on stack
items.add("Item " + i); // String objects on heap
}
// Local reference variable on stack pointing to existing heap object
List<String> sameItems = items; // 'sameItems' on stack, points to same ArrayList
// When method ends, 'count', 'items', and 'sameItems' references are removed from stack
// The ArrayList remains on heap if referenced elsewhere, otherwise eligible for GC
}

Java is strictly pass-by-value, but the values passed can be references to objects:

public void demonstratePassByValue() {
int number = 10; // Primitive on stack
Person person = new Person("Alice", 30); // Reference on stack, object on heap
modifyValues(number, person); // Copies of values are passed
System.out.println(number); // Still 10
System.out.println(person.getName()); // Now "Bob" (object was modified)
}
private void modifyValues(int num, Person p) {
num = 20; // Modifies local copy, doesn't affect original
p.setName("Bob"); // Modifies the object that p refers to
}

Despite automatic garbage collection, memory leaks can still occur when objects remain referenced but are no longer needed:

public class CacheManager {
// Static references persist for application lifetime
private static final Map<String, LargeObject> cache = new HashMap<>();
public void addToCache(String key, LargeObject object) {
cache.put(key, object); // Objects never removed, potential memory leak
}
// Missing cache eviction strategy
}
  1. What’s the difference between stack and heap memory in Java?

    • Stack is thread-specific, LIFO structure for method execution and local variables
    • Heap is shared across threads for object storage and managed by garbage collection
  2. Where are local variables stored in Java?

    • Primitive local variables are stored on the stack
    • Object references are stored on the stack, but the objects themselves are on the heap
  3. What happens when a method is called in terms of memory allocation?

    • A new frame is created on the stack containing method parameters, local variables, and return value
    • Any objects created within the method are allocated on the heap
  4. Can a Java application run out of stack memory? How?

    • Yes, via StackOverflowError from infinite recursion or very deep call hierarchies
  5. How do you tune stack and heap memory in JVM?

    • Stack: -Xss parameter (e.g., -Xss1m for 1MB stack size)
    • Heap: -Xms (initial) and -Xmx (maximum) parameters (e.g., -Xms4g -Xmx8g)
  6. Why doesn’t Java support true pass-by-reference?

    • Java’s design decision for simplicity and security
    • All parameters are passed by value, though the values can be references to objects
  7. How does garbage collection know which objects to remove from heap?

    • It identifies objects that are no longer reachable from GC roots (thread stacks, static fields, etc.)
  8. What’s the relationship between stack memory and thread safety?

    • Each thread has its own stack, so local variables are inherently thread-safe
    • Heap objects are shared and require explicit synchronization for thread safety
  9. What happens to the memory when a thread terminates?

    • The thread’s stack memory is reclaimed
    • Objects on the heap remain until no longer referenced by any thread
  10. How do you diagnose and fix memory leaks in Java applications?

    • Use profiling tools (JVisualVM, YourKit, etc.) to analyze heap dumps
    • Look for growing collections, caches without eviction policies
    • Check for unclosed resources (streams, connections)
    • Review static collections and listener registrations