This page collects notes about the use by Java of the hardware control stack.

Interpreter Stack Frames

An interpreter stack frame consists of four main parts: a variable part containing locals is shared with the caller, a fixed part is addressed by the frame pointer FP, a variable below that contains zero or more locally locked monitors, and a variable part just above SP contains the JVM temporary stack. (On x86 this last part is directly addressed by SP (rsp). Some CPUs other than x86 make alignment requirements on SP, or require information to be stored there, such as register windows. On these CPUs, a separate register may be used to point to the Java VM stack top, inside the variable part, which varies more slowly.) The fixed part contains the basic state variables for the interpreter, plus information for sizing the variable part.

The machine-dependent code for frame layout is in files like frame_x86.hpp, which are included into type type frame.

Here is the layout (which is typical) for the x86 assembly-based interpreter. It is set up by InterpreterGenerator::generate_normal_entry. Items are presented in descending order of memory:

last_sp

The value last_sp is stored by InterpreterMacroAssembler::prepare_to_jump_from_interpreted and read by TemplateInterpreterGenerator::generate_return_entry_for, which pops the arguments (very late) in the expression lea(rsp, ...). So last_sp points (dangerously) to outgoing arguments owned by the callee. Note that the callee is free to modify those arguments, and even change oops to ints and vice versa, since they are the callee's locals. With invokedynamic, low-level adapters between the caller and callee may also rearrange the arguments. All these changes would look to the caller like scribbling. Therefore, even though the caller has a last_sp which appear to point at an outgoing argument list, it would be an error to read or write that memory.

In frame::oops_interpreted_do there is a special stanza (guarded by map->include_argument_oops()) to handle the outgoing arguments; it is used only in cases where a call has blocked in the linker and the callee has not been determined yet. Outgoing argument oops will never be processed twice; this would cause duplication in the root set which breaks the GC (throwing asserts, in debug builds). The logic that prevents this from happening is inside OopMapCache::compute_one_oop_map, which avoids pointing the InterpreterFrameClosure at the wrong parts of the expression stack which might have been scribbled. (Even though last_sp points at those wrong parts.)