skip to content

What does a single JVM stack frame contain, and at what point are the sizes of its parts decided?

level: middleimportance: must knowfreq 55%

answer

  1. locals array + operand stack + constant-pool ref
  2. max_locals / max_stack in the Code attribute
  3. sizes fixed at compile time, verified before execution
  4. slot 0 = this for instance methods
  5. frame data: return address, previous frame, method

basics

~20 s

A frame holds a local variable array, an operand stack, and a reference to the run-time constant pool of the method's class, plus implementation bookkeeping such as the return address. The two sizes are fixed at compile time and stored in the method's Code attribute, so the frame's size is known before it is pushed.

solid answer

~50 s

The specification gives a frame three named parts: 1. **Local variable array** — indexed slots holding the parameters (slot 0 is `this` for an instance method) and the declared locals. 2. **Operand stack** — the working area where bytecode pushes operands, calls take their arguments from, and results come back to. 3. **A reference to the run-time constant pool** of the class declaring the current method, used to resolve symbolic references to other classes, fields and methods. Implementations add *frame data*: where to resume in the caller, which method this frame is for, links to the previous frame, and exception-handling information. Crucially, the sizes are **not** dynamic. `javac` computes `max_locals` and `max_stack` for each method and records them in the `Code` attribute; the verifier checks them. So the JVM knows the exact frame size before pushing it — allocation is a pointer bump on the stack, and depth limits are predictable per method.

code

text · 9 lines
text
static int add(int, int);
  Code:
    stack=2, locals=3, args_size=2
       0: iload_0        // push a  (local slot 0)
       1: iload_1        // push b  (local slot 1)
       2: iadd           // pop 2, push sum   -> peak operand depth 2
       3: istore_2       // pop into local slot 2 (c)
       4: iload_2
       5: ireturn        // pops the value; it lands on the CALLER's operand stack

go deeper

for a junior

Name the three parts — local variables, operand stack, constant-pool reference — and say the frame is created on invocation and destroyed on return.

for a middle

Add that max_locals/max_stack come from the class file, are verified up front, and therefore make frame allocation a fixed-size push; mention slot 0 holding this.

for a senior

Explain the consequences: cheap allocation, an operand stack that cannot overflow, per-method frame sizes affecting achievable depth, and that JIT frames differ while keeping metadata for stack traces and deoptimization.

for a principal

Position it as a design choice — a statically sized, verifiable frame is what lets the runtime allocate frames for free, walk stacks precisely for GC and profiling, and swap between interpreted and compiled frame layouts safely.

## A frame is the record of one invocation When a thread invokes a method, the JVM pushes a **frame** onto that thread's stack. The frame exists for exactly as long as the invocation does. The specification defines three components, and every real implementation adds a fourth category of bookkeeping. ### 1. The local variable array An array of slots addressed by index, each slot wide enough to hold an `int`, `float`, `reference`, or `returnAddress`. Index 0 of an **instance** method holds the receiver, `this`; for a **static** method index 0 is the first parameter. Parameters occupy the low indices in declaration order, followed by the locals declared in the body. Values of type `long` and `double` occupy **two consecutive slots**, addressed by the lower index. The compiler may reuse one slot for several source variables whose scopes do not overlap, which is why `max_locals` is often smaller than the number of names in the source. Bytecode reaches locals only through explicit load/store instructions (`iload_1`, `astore_2`, and so on) — arithmetic never operates on a local in place. ### 2. The operand stack A last-in-first-out working area, empty when the frame is created. Bytecode loads values from locals or constants onto it, instructions such as `iadd` pop their operands and push the result, and `istore` moves a result back into a local. It is also the calling protocol: to invoke a method the caller pushes the receiver (for instance calls) and then the arguments; the invoke instruction pops them and they become the *callee's* locals 0..n; when the callee returns a value, that value is pushed onto the **caller's** operand stack. `long` and `double` occupy two units of depth here as well. ### 3. The reference to the run-time constant pool Bytecode names other classes, fields and methods symbolically — `#7` rather than an address. The frame carries a reference to the run-time constant pool of the class that declares the current method, so an instruction like `invokevirtual #7` can resolve the symbolic reference (lazily, on first execution, with the result cached) to something directly usable. This is what makes late binding and lazy class loading work. ### 4. Implementation frame data Not named as a separate component by the specification, but every implementation needs: the **return address** (where to resume in the caller), a pointer to the method being executed, a link to the previous frame so a stack walk can proceed, and information used to find the right exception handler and to release monitors held by a `synchronized` method when the frame unwinds. ## Why the sizes are fixed at compile time Each method's `Code` attribute in the class file records two numbers computed by the compiler: - `max_locals` — how many local slots the method needs. - `max_stack` — the maximum operand stack depth it will ever reach. The verifier proves, before the method is ever executed, that the bytecode never exceeds either bound and that types at every point are consistent. Three consequences follow, and they are what an interviewer is usually probing for: 1. **Frame allocation is trivially cheap.** The size is known before the push, so it is an arithmetic adjustment of the stack pointer — no search, no allocator, no per-frame metadata to build. 2. **The operand stack cannot overflow at run time.** Depth is statically bounded, so "stack overflow" in the JVM always means *too many frames*, never a single frame's operand stack growing too large. 3. **Different methods have different frame sizes.** A method with many locals produces a fatter frame, so the maximum call depth a thread can reach depends on which methods are on the chain, not on some fixed count. ## Interpreted versus compiled frames The layout above describes the interpreter's view, and it is the model you must be able to describe. Once the JIT compiles a method, the machine-code frame follows the platform's calling convention: values live in registers where possible, the operand stack largely disappears, and the frame layout is whatever the compiler chose. The JVM keeps enough metadata (oop maps and debug information at defined points) to reconstruct the specification-level view — which is what makes accurate stack traces, and deoptimization back to the interpreter, possible. ## A worked example For `static int add(int a, int b) { int c = a + b; return c; }` the compiler emits roughly `iload_0; iload_1; iadd; istore_2; iload_2; ireturn`, with `max_locals = 3` (a, b, c) and `max_stack = 2` (two operands live at once). Read those two numbers with `javap -c -v` and you have read the frame's dimensions directly.

  • If `max_stack` is fixed and verified, can a single method invocation ever overflow its operand stack at run time?
    No. The verifier proves before first execution that no reachable path exceeds `max_stack`, so the operand stack depth is statically bounded. Stack exhaustion in the JVM always means the thread accumulated too many frames — depth of the call chain — not that one frame's working area grew too large.
  • Does every source-level local variable get its own slot in the local variable array?
    Not necessarily. The compiler may assign the same slot to two variables whose live ranges do not overlap, so `max_locals` can be smaller than the count of declared names. Conversely a single `long` or `double` variable consumes two consecutive slots, so the slot count is not the variable count either.

saying these in an interview costs you the question

  • Claiming the operand stack grows dynamically as the method runs — its maximum depth is fixed at compile time and verified.
  • Saying frame size is decided at run time based on how the method behaves.
  • Confusing the operand stack (a working area inside one frame) with the thread stack (the stack of frames).
  • Asserting `max_locals` equals the number of declared variables, ignoring slot reuse and the two-slot rule for long/double.
  • Saying each frame carries its own copy of the constant pool rather than a reference to the class's run-time constant pool.

context