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...
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
- 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:
- Increase
-Xssto 2 MB (temporary) - Rewrite parser as iterative (permanent)
- Increase
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
- Stream API for complex transformations
- StackWalker for call chain analysis
- Avoid deep recursion — use iteration
- Virtual Threads (Java 21+) for high-concurrency. Do not use for CPU-bound tasks — they give no benefit and add mount/unmount overhead.
- 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,
-Xssis 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]]