JVM Execution: Stack + Locals
The Java Virtual Machine (JVM) executes method bytecode using a stack-based architecture. Each thread in a Java program has its own private JVM stack, which consists of stack frames. A new stack frame is created and pushed onto the stack every time a method is invoked. When the method completes (normally or via a thrown exception), its frame is popped and discarded. Each stack frame represents the state of a single method invocation and contains:
- Local Variables: An array of variables holding primitive and reference types. Contents are stored and accessed in the array via the variable's index (Recaf's assembler will use the name of variables rather than its index).
- Operand Stack: A Last-In-First-Out (LIFO) structure used as temporary space for intermediate values during computations.
The JVM also maintains a program counter (PC) per thread, which points to the current bytecode instruction being executed in the method. The PC advances sequentially as instructions are processed, unless modified by control flow instructions.
Execution Flow Overview
- When a method is invoked:
- A new frame is created for the invoked method.
- The caller pushes copies of the argument values (primitive values or copies of object references) onto the invoked method's operand stack.
- Execution in the caller is paused at the instruction immediately after the method invocation, ready to resume once the invoked method completes its own execution and (if applicable) pushes a return value onto the caller's operand stack.
- Instructions execute one by one:
- Load constants or variables → push to the operand stack.
- Store result → pop from the stack to a variable.
- Arithmetic/logic → pop operands, compute, push result.
- Control flow → conditionally jump to a label.
- Invoke methods → see above description of method invocation.
- On return:
- The frame is popped, and the value on top of the stack (if any is present) is pushed to the caller's operand stack.
Examples
Local variables and arithmetic
// int two = 2;
iconst_2
istore two
// int ten = 10;
bipush 10
istore ten
// int result = 0;
iconst_0
istore result
// result = ten * ten;
iload ten
dup
imul
istore result
// result -= 98;
iinc result -98
// result -= (int) Math.pow(result, 6);
iload result
iload result
i2d
ldc 6D
invokestatic java/lang/Math.pow (DD)D
d2i
isub
istore result
// return result;
iload result
ireturn
Control flow handling of a for loop
// int sum = 0;
iconst_0
istore sum
// int i = 0;
iconst_0
istore i
// Start of for loop
FOR:
// if (i >= 0) break;
iload i
bipush 100
if_icmpge EXIT
// sum += i;
iload sum
iload i
iadd
istore sum
// i++;
iinc i 1
goto FOR
// End of for loop
EXIT:
// return sum;
iload sum
ireturn