In the JVM, what is a thread stack, and what is pushed onto it while a method executes?
answer
- one stack per thread, born and dies with it
- one frame per invocation: push on call, pop on return
- frames top-to-bottom = the stack trace
- locals hold references; objects live in the heap
- stack memory is native, not heap, and bounded
basics
~20 sEvery JVM thread gets its own stack, created with the thread. Each method invocation pushes one frame holding that call's local variables and working values; returning pops it. Stacks are private to their thread and disappear when the thread ends.
solid answer
~50 sEach thread in the JVM has a private **JVM stack**, allocated when the thread starts and released when it dies. The stack is a stack of **frames**: invoking a method pushes a new frame, and the frame is popped when the method returns normally or completes abruptly with an exception. The frame holds everything local to that one invocation — the method's parameters and local variables, and the working area the bytecode uses to compute expressions. Because the stack belongs to one thread, nothing on it is shared: two threads running the same method have completely separate copies of its locals, which is why locals need no synchronization. Objects themselves are not on the stack — a local variable of reference type holds only a reference to an object that lives elsewhere. Stack space is finite and fixed per thread, so a call chain that goes too deep — typically unbounded recursion — runs out of frames.
go deeper
Be able to say: one stack per thread, one frame per method call, pushed on invocation and popped on return, and the frame holds that call's locals.
Add that a frame also has an operand stack and a constant-pool reference, that stack memory is native and bounded per thread, and that locals hold references to heap objects.
Connect it to practice: thread count versus native memory, why locals are thread-confined but their referents are not, and why a stack trace is literally the live frame list.
Frame it as a design constraint — bounded per-thread stacks are what make thread-per-request models expensive, and what virtual threads' heap-resident stack chunks were introduced to change.
## The JVM's per-thread execution stack The Java Virtual Machine is a stack machine. Every thread the JVM runs — whether it started as `new Thread(...)`, came from a pool, or is an internal VM thread running Java code — is given its own **JVM stack** at the moment the thread is created, and that stack is discarded when the thread terminates. Nothing else in the process can reach it. A stack is not an undifferentiated block of scratch memory; it is a stack of **frames**. A frame is the bookkeeping record for exactly one method invocation. The rule is simple and absolute: - Invoking a method **pushes** a new frame, which becomes the *current frame*, and the method it belongs to becomes the *current method*. - The method returns — normally, via a `return` instruction, or abruptly, because an exception propagates out of it — and its frame is **popped**. The caller's frame becomes current again, and execution resumes at the instruction after the call. So at any instant, the frames on a thread's stack, read from top to bottom, *are* the call chain of that thread. That is precisely what a stack trace prints: one line per live frame. ## What lives in a frame A frame carries the state that belongs to one invocation and to nothing else: - **Local variables** — the method's parameters (including the receiver, `this`, for an instance method) plus every local declared in the body, stored in an indexed array of slots. - **The operand stack** — a small working area where the bytecode pushes and pops values while evaluating expressions, passing arguments to a nested call, and receiving that call's result. - **A reference to the run-time constant pool** of the class that declares the method, used to resolve the symbolic names of the other classes, fields and methods that the code touches. An implementation adds its own bookkeeping — where to resume in the caller, which method this frame belongs to, and so on. ## Stack versus heap A very common confusion: "objects with short lifetimes go on the stack." In the JVM, objects are created by `new` in the shared heap, and a local variable of reference type holds only a *reference* to one. Two things follow. First, when a frame pops, its locals vanish — but the objects they pointed to do not; they simply may become unreachable, and the collector deals with them later. Second, two threads executing the same method genuinely have separate locals, because each has its own stack. That is the deep reason local variables are automatically thread-safe while fields of a shared object are not. (The JIT compiler can, after proving an object never escapes its method, avoid allocating it at all — but that is an optimization applied to compiled code, not a rule of the language, and you cannot rely on observing it.) ## Stacks are bounded A thread's stack has a fixed maximum size chosen at thread creation. Every live frame consumes part of it, so the depth of a call chain is bounded. Ordinary code never comes close; unbounded recursion — a method that calls itself with no terminating case, or a cycle such as two `toString()` implementations calling each other — walks off the end, and the JVM reports the condition rather than corrupting memory. Two smaller consequences worth knowing: - Stack memory is **not** taken from the Java heap. It is native memory reserved per thread, which is why creating tens of thousands of platform threads costs real memory outside the heap even if each is idle. - The stack is also not garbage collected. Frames are reclaimed deterministically by returning, which is why a runaway recursion fails immediately and loudly instead of gradually. ## Where the frames physically sit The specification deliberately does not say a stack must be a contiguous native memory block; it only requires the push/pop discipline. Mainstream implementations do use a native stack per platform thread. Virtual threads (standard since Java 21) exploit the freedom: when a virtual thread parks, its frames are copied into heap-managed stack chunks so that millions of them can exist, and copied back when it resumes. The programming model is unchanged — one frame per invocation, pushed on call and popped on return.
- If local variables are private to a thread, does that make the objects they reference thread-safe too?No. Only the variable — the slot holding the reference — is thread-confined. If two threads end up with references to the same object, they share that object's fields and need synchronization exactly as before. Thread confinement is a property of the reference, not of what it points to.
- Why does creating very many platform threads cost memory even when they are idle?Each platform thread gets its own stack reserved in native memory outside the Java heap, on the order of hundreds of kilobytes to a megabyte by default. The address space is reserved up front even though pages are committed lazily as the stack grows, so thread count is bounded by native memory and OS limits rather than by heap size.
A stack of index cards on a desk: starting a task lays a fresh card on top with only that task's scratch notes; finishing it tears the card off and you continue on the card underneath. Each worker has their own pile — no one writes on anyone else's.
saying these in an interview costs you the question
- Saying objects created inside a method are allocated on the stack — `new` allocates in the heap; the local only holds a reference.
- Believing all threads share one stack, or that the stack is part of the Java heap.
- Claiming stack frames are garbage collected; they are popped deterministically on return.
- Thinking a frame survives the method that created it so its locals can be read later.