How a method call works
What the machine does between the call and the return: the call stack, one frame per method, and why locals vanish when it finishes.
Calling a method looks like one step in the source and is several at runtime. Knowing what those steps are explains a lot of otherwise arbitrary behaviour, why local variables do not survive a return, why recursion has a depth limit, and what a stack trace is a picture of.
The call stack
Call stack. The memory used to hold the return address and the local variables of every method currently executing.
Stack frame: the block on that stack belonging to one method call. One call, one frame. Call a method from inside a method and a second frame goes on top of the first.
The stack grows as calls are made and shrinks as they return, so its height at any moment
is the depth of nesting you are currently at. main is at the bottom, or nearly: there
is machinery beneath it, and any library method you call has frames of its own, none of
which you need to care about.
What happens on a call
1. Evaluate the arguments, left to right. A literal or a plain variable needs no work. An expression is worked out first, and the order matters when the expressions have side effects.
2. Push a new frame. It holds space for the parameters and local variables, the point to resume at when the method returns, and working storage the runtime needs. Only the first is yours to think about.
3. Initialise the parameters. The evaluated argument values are assigned into the parameter variables of the new frame. They are copies, assigning to a parameter changes only this frame’s copy, which is the whole of what “pass by value” means.
4. Run the method. Execution starts at the first statement. If it calls something else, the same sequence happens again one frame higher.
5. Return. At a return, or at the closing brace of a void method, the frame is
discarded and execution resumes where it left off. A returned value is handed back to the
caller as the value of the call expression.
Step 5 is why a local variable cannot outlive its method: the storage it lived in was the frame, and the frame is gone.
Why it is worth having the picture
Stack traces read bottom-up. The trace printed by an exception is the stack at the moment it was thrown: the frame at the top is where it happened, and each line below is the call that led there.
Recursion is bounded by stack size, not by cleverness. Each recursive call is another
frame, and a recursion that fails to terminate exhausts the stack rather than running
forever, which is what StackOverflowError is telling you.
Deep call chains are not free, though the cost is small enough that it is almost never the thing worth optimising.