Skip to content

VOL 01 / CH 05 / LESSON 05

5.5 Call Stack, Stack Frames, and Stack Traces ​

Prerequisites: Method Parameters and Return Values Estimated time: 35 minutes You are ready to continue when: You can reverse-engineer the call chain from a stack trace and understand runtime optimization boundaries

"This is the model of a call stack: the most recently entered method returns first."

Each Thread Has Its Own JVM Stack ​

When a thread calls a method, the JVM creates a frame for the call. At the level of specification, a frame includes a local variable area, an operand stack, and references to the current class's constant pool. It also participates in returning control and exception propagation.

text
JVM Stack of a Thread (Conceptual Diagram)
┌──────────────┐
│ calculateTax │ ← Currently executing
├──────────────┤
│ checkout     │
├──────────────┤
│ main         │
└──────────────┘

"The frame is just local variables + return address" is suitable for the first step, but it's incomplete. "Each frame is several to hundreds of bytes" is not a portable guarantee. Frame layout, stack size, and optimization are determined by the JVM implementation, method bytecode, and runtime parameters.

Observing Entry and Return with Logs ​

java
public class StackDemo {
    static int add(int left, int right) {
        System.out.println("2. enter add");
        int result = left + right;
        System.out.println("3. leave add");
        return result;
    }

    public static void main(String[] args) {
        System.out.println("1. enter main");
        int result = add(3, 4);
        System.out.println("4. result=" + result);
        System.out.println("5. leave main");
    }
}

When calling add, main has not yet completed, and its execution context remains in the lower layer. add returns, and the call site receives the result, allowing main to continue.

Exception Propagation Along the Call Stack ​

java
static void load() {
    parse();
}

static void parse() {
    Integer.parseInt("not-a-number");
}

public static void main(String[] args) {
    load();
}

If there is no matching catch point, the exception propagates from parse to load, then to main, and the thread ultimately ends with an uncaught exception. The stack trace typically starts by showing the exception type and message, followed by the point of throwing and the caller:

text
NumberFormatException: ...
    at ...parse(...)
    at ...load(...)
    at ...main(...)

When debugging, first find the earliest relevant frame that belongs to your code, then read down through its callers to understand where the input originates. Frameworks, reflection, and asynchronous boundaries can either add or remove intuitive links; Caused by also represents the causal chain of the exception, which is not equivalent to a regular call hierarchy.

Recursion Adds Active Calls ​

java
static long factorial(int value) {
    if (value < 0) {
        throw new IllegalArgumentException("negative value");
    }
    if (value > 20) {
        throw new ArithmeticException("factorial exceeds long: " + value);
    }
    if (value <= 1) {
        return 1L;
    }
    return Math.multiplyExact(value, factorial(value - 1));
}

Each layer remains active until the next layer returns. The absence of a reachable termination condition may trigger StackOverflowError.

Do not write "recursion depth exceeds a certain fixed number and it overflows." The available depth is influenced by thread stack configuration, JVM, platform, and compilation state, as well as the needs of each frame. StackOverflowError belongs to Error, and it is usually not advisable to catch it and pretend the program can safely continue; instead, fix the algorithm or input boundaries.

JIT Optimization and Conceptual Model ​

The JVM can just-in-time compile and inline small methods, so the physical machine stack does not necessarily correspond one-to-one with source code methods. The JVM must maintain the observable semantics of Java and can reconstruct or report sufficient state when needed. The stack diagram in the tutorial represents a language/JVM model of calls, local scopes, returns, and exceptions, not a real-time snapshot of processor registers and machine frames.

This also shows that the statement "writing a small method definitely adds an expensive stack call" is not valid. Write clearly and measure the hot spots first, then optimize based on the analysis results.

Each Thread's Stack Is Independent ​

When two threads call the same method, each has its own parameter and local variable frame. They do not share these local variables even if they have the same name. They may still influence each other through static fields, shared objects, files, or databases. Thread safety issues mainly arise from shared mutable state, not from the names of local variables.

Professional Quick Reference ​

TargetObservation Point
Restore synchronous call chainStack trace frame order and source code line
Identify root causeException type, first relevant business frame, Caused by chain
Determine local isolationPer-thread independent frames; check if references point to shared objects
Recursion safetyReachable base, input upper bound, stack depth not fixed, numeric overflow
Performance judgmentDo not guess from number of source code methods; use profiler/JFR etc. for measurement

Official reference: JVMS 17 §2.5.2 Java Virtual Machine Stacks and §2.6 Frames.

Before Moving On ​

Draw the activation frame sequence of main → checkout → calculateTax, and separately mark the call chain and exception causality chain from a stack trace containing Caused by. Frame sizes, recursion limits, and the mapping from source methods to physical frames are not fixed across JVMs.

→ Next: Introduction to Recursion

Built with VitePress | Software Systems Atlas