Question 3 · Section 3

What is stored in Stack?

4. Virtual Threads (Java 21+) for high-concurrency. Do not use for CPU-bound tasks — they give no benefit and add mount/unmount overhead. 5. Escape Analysis helps JIT optimize a...

Language versions: English Russian Ukrainian

Junior Level

Stack — a private memory area for each thread.

Why each thread has its own stack: each thread has its own chain of method calls. Threads cannot share one Stack — otherwise local variables of one thread would be visible to another.

Simple analogy: A stack of plates. When you call a method — you put a plate on top. When the method returns — you remove the plate. You always work with the top plate.

What is stored:

  • Primitives: int x = 10;, double price = 99.9;
  • References to objects: User user = ...; (reference in Stack, object in Heap)
  • Method call information

Example:

public void methodA() {
    int a = 5;         // Stack
    methodB();         // New frame on stack
}

public void methodB() {
    int b = 10;        // Stack (in its own frame)
    // When method returns → b is removed
}

Stack Size: -Xss (default 1 MB) Error: StackOverflowError on infinite recursion


Middle Level

Stack Frame Structure

Stack Frame (created on method call):
┌─────────────────────────────────┐
│ Local Variable Table            │
│   [0] = this                    │
│   [1] = param1                  │
│   [2] = param2                  │
│   [3] = local var               │
├─────────────────────────────────┤
│ Operand Stack                   │
│   For computations: push, pop   │
│   a + b → push a, push b, add   │
├─────────────────────────────────┤
│ Dynamic Linking                 │
│   → Constant Pool (methods, fields)│
├─────────────────────────────────┤
│ Return Address                  │
│   → Where to return             │
└─────────────────────────────────┘

What exactly is in Stack

public class Example {
    public void process(int x) {
        int local = x * 2;        // Stack (primitive)
        String s = "hello";       // Stack (reference) → "hello" in Heap
        User user = new User();   // Stack (reference) → object in Heap
        int[] arr = new int[10];  // Stack (reference) → array in Heap
    }
}

IMPORTANT: Only references to objects are stored in Stack, not the objects themselves!

StackWalker API (Java 9+)

// Old way (heavyweight)
StackTraceElement[] stack = new Throwable().getStackTrace();

// Modern way (lazy, fast)
StackWalker walker = StackWalker.getInstance();
walker.forEach(frame -> System.out.println(frame.getClassName()));

// With filtering
String caller = StackWalker.getInstance()
    .walk(frames -> frames
        .skip(1)  // Skip current method
        .findFirst()
        .map(StackWalker.StackFrame::getClassName)
        .orElse(null));

Common mistakes

  1. Deep recursion
    // ❌ StackOverflowError
    public int factorial(int n) {
        return n * factorial(n - 1);  // No base case!
    }
    

Senior Level

Stack Frame: Under the Hood

JVM Specification:
  Frame is created on method call
  Size is computed at compile time (known number of local variables)

Local Variable Table:
  - Array of words (32 bits each)
  - long/double take 2 slots
  - Index 0 = 'this' for non-static methods
  - Method parameters come first

Operand Stack:
  - Depth is also known at compile time
  - Used for byte-code instructions
  - Example: a + b + c
    → push a → push b → iadd → push c → iadd

Virtual Threads and Stack (Java 21+)

Platform threads (before Java 21):
  Stack = fixed chunk of native memory (1 MB via -Xss)
  → Limitation: ~1000 threads per 1 GB RAM
  → Cannot resize on the fly

Virtual threads (Java 21+):
  Stack = object in Heap!
  → Mount: copied into Carrier Thread stack
  → Yield (blocking): copied back into Heap
  → Result: millions of threads!

Memory per virtual thread:
  → ~2 KB — initial value for an empty virtual thread. Stack grows dynamically as call depth increases.
  → Dynamically grows when needed

Tail Call Optimization (TCO)

// Tail recursion
public int sum(int n, int acc) {
    if (n == 0) return acc;
    return sum(n - 1, acc + n);  // Tail call
}

// In languages with TCO → same frame is reused
// Java does NOT support TCO directly!
// But JIT does Method Inlining:

// After Inlining:
public int sum(int n, int acc) {
    while (n != 0) {
        acc += n;
        n--;
    }
    return acc;
}
// → 1 frame instead of N!

Stack and Escape Analysis

// JIT may allocate object on Stack!
public void process() {
    Point p = new Point(10, 20);  // Usually in Heap

    // If JIT sees that 'p' doesn't "escape" the method:
    // 1. Scalar Replacement: break into int x=10, y=20
    // 2. Stack Allocation: create on Stack
    // 3. Lock Elision: remove synchronized if single thread

    System.out.println(p.x + p.y);  // Used only here
}

// Check: -XX:+PrintEscapeAnalysis -XX:+DoEscapeAnalysis

Stream API as Iterator

// Stream = advanced Iterator with lazy evaluation
list.stream()
    .filter(s -> s.startsWith("A"))   // Not executed until terminal
    .map(String::toUpperCase)         // Not executed
    .forEach(System.out::println);    // Terminal → launches pipeline

// Unlike Iterator:
// 1. Composability — can build chains
// 2. Lazy — computations only when needed
// 3. Parallel — automatic parallelism
// 4. Functional — no side effects

Production Experience

Real scenario: StackOverflowError in production

  • Deep nested JSON parsing → recursion 10,000+ levels
  • -Xss = 1 MB → StackOverflowError
  • Solution:
    1. Increase -Xss to 2 MB (temporary)
    2. Rewrite parser as iterative (permanent)

Real scenario: Virtual Threads saved the server

  • REST API: 10,000 concurrent requests
  • Platform threads: 10,000 × 1 MB = 10 GB RAM for stacks
  • Virtual threads: 10,000 × ~2 KB = 20 MB in Heap
  • Savings: 99.8% RAM!

Best Practices

  1. Stream API for complex transformations
  2. StackWalker for call chain analysis
  3. Avoid deep recursion — use iteration
  4. Virtual Threads (Java 21+) for high-concurrency. Do not use for CPU-bound tasks — they give no benefit and add mount/unmount overhead.
  5. Escape Analysis helps JIT optimize allocations

Senior Summary

  • Stack = fragmented execution memory, frame-per-frame
  • Virtual Threads = stack in Heap → dynamic sizing → millions of threads. Not for CPU-bound tasks.
  • StackWalker = lazy stack inspection, +performance
  • Escape Analysis = Stack Allocation + Lock Elision
  • TCO not supported, but Method Inlining solves it
  • Stream API = lazy Iterator with composability

Interview Cheat Sheet

Must know:

  • Stack stores: local primitives, references to objects, method call information
  • Each thread has its own Stack (default 1 MB, parameter -Xss)
  • Stack frame: Local Variable Table + Operand Stack + Dynamic Linking + Return Address
  • On method return, frame is removed instantly — no GC needed
  • Virtual Threads (Java 21+): stack in Heap as object, ~2 KB instead of 1 MB
  • JIT can allocate object on Stack via Escape Analysis if it doesn’t “escape”
  • Java does not support Tail Call Optimization directly, but JIT does Method Inlining

Common follow-up questions:

  • What happens on infinite recursion?StackOverflowError, Stack overflowed
  • Why do Virtual Threads allow millions of threads? — Stack in Heap, dynamic size (~2 KB vs 1 MB fixed)
  • Can Stack size be changed on the fly? — No, -Xss is set at JVM startup. Virtual Threads solve this with dynamic sizing.
  • What is Operand Stack? — Operand stack inside frame for byte-code instructions (push/pop/add)

Red flags (DO NOT say):

  • “Objects are stored in Stack” — only references to objects are stored in Stack
  • “Stack is shared across all threads” — Stack is private to each thread
  • “Java supports TCO” — Java does NOT support Tail Call Optimization

Related topics:

  • [[1. What is the difference between Heap and Stack]]
  • [[2. What is stored in Heap]]
  • [[4. What is Garbage Collection]]
  • [[16. What is stop-the-world]]
  • [[18. What are -Xms and -Xmx parameters]]