serversim
LearnSimulatorTemplatesPricing
…
…
Open menu
Learn↗Simulator↗Templates↗Pricing↗
My DocumentsDocs
Contents/Chapter 33 / 16

Stack and heap — what goes where

Why do some values vanish when a function returns while others stay behind? Notes on your desk get cleared away when the task ends; a box you put in the storeroom stays until someone asks for it.

Program memory splits into two regions, and the difference comes down to "when does it disappear" and "is the size known in advance". Get this right and three things click at once: why some values vanish when a function returns, why passing a huge object is cheap, and why memory problems almost always live in the heap.

① What lives on the stack

Every call pushes a frame holding three things: parameters, the function's locals, and the return address. The size is fixed at compile time, which makes allocation nearly free — move a pointer up to allocate, down to release.

② What piles up on the heap

Arrays, objects and strings whose size is decided at run time, or which must outlive the call, live on the heap: a big warehouse that hands out free space on demand. Finding and managing that space costs more than the stack.

③ The stack shares the function's fate

When a function returns its frame vanishes along with its locals — but a heap object survives as long as something holds its address. Watch login's frame disappear below while the User object stays.

④ That is why passing big objects is cheap

Passing an array copies the tag, not the array, so a million-element array costs the same as copying one number. The side effect: mutating it inside the function changes it outside too — same box.

⑤ Stack overflow, heap growth

Deep calls stack up, and endless recursion overflows the limit. The heap instead suffers from boxes nobody uses piling up — which is the next chapter. Watch the stack grow three frames deep and shrink while heap boxes remain.

Step 1/9Speed
program
1main() {
2 user = login("danbi")PC ◀
3 cart = ["book"]
4 total = checkout(user, cart)
5 print(total)
6}
7login(name) {
8 return User(name)
9}
10checkout(u, items) {
11 sum = 0
12 for it in items: sum += price(it)
13 return sum
14}
15price(it) { return 12000 }

main starts and a frame is placed on the stack, with slots for its locals (user, cart, total).

CPU0
staterunningPC · position→ line 2
Stack↑ newest frame on top
main()
user = ?cart = ?total = ?
Heap
∅

Try it in the simulator

  1. 1.Open the 'GC pressure demo' preset — the Spring node's JVM heap (MB) is set to 64. A 512KB response buffer fills that heap faster than the CPU can keep up.
  2. 2.Run it for 10 seconds and check the KPI panel's p99 — it spikes to 91.9ms. Set JVM heap (MB) back to 2,048 in the inspector and, at the same load, p99 drops to 51.2ms.
  3. 3.This is the stack/heap split from this chapter in action — the stack is unaffected; it is heap objects (the response buffers) piling up faster than they can be reclaimed.
Try it in the simulator →

Terms in this chapter

stack frame
One function's space: parameters + locals + return address.
heap
The region for data that changes size or outlives a call.
reference
Holding a location rather than a value — the @1 notation here.
by value / by reference
Small values are copied outright; objects have only their tag copied.
stack overflow
Exceeding the stack limit, usually via unbounded recursion.

Why it evolved this way

Why two regions? With only a stack there is nowhere for data that must outlive a call; with only a heap every local variable would require a warehouse lookup. Short-lived fixed-size data on a blazing-fast stack, large long-lived data on a flexible heap — that division is near-universal across languages.

The stack dies with the function; the heap lives as long as something points at it.

The remaining problem

So who cleans up the heap boxes, and when? →

← Previous

Chapter 2. How a line of code runs

Next →

Chapter 4. Why memory fills up — and GC