skip to content

The JVM specification states that a thread's program counter register value is undefined while that thread is executing a native (JNI) method. Why, and what does that imply for stack traces?

level: middleimportance: nice to knowfreq 26%

answer

  1. native code has no bytecode offsets
  2. hardware PC drives native execution
  3. stack trace prints Native Method, no line
  4. caller's Java frame keeps its own resume bci
  5. threads in native are already safepoint-safe

basics

~20 s

The PC register addresses JVM bytecode. Native code is machine code with no bytecode offsets, and its execution point is tracked by the hardware program counter, so the JVM promises nothing about the value until the thread returns to a Java frame.

solid answer

~60 s

The pc register is defined to hold the address of the JVM instruction being executed. That only makes sense while the thread is in a Java method, where the execution point is an offset into a bytecode array. A native method has no bytecode at all: it is platform machine code reached through JNI, and while it runs, the real instruction pointer is the CPU's, managed by the operating system and the native calling convention. Rather than inventing a meaning, the specification declares the pc register undefined for that period. The consequence is visible in stack traces: a native frame prints as `Native Method` with no line number, because there is no bytecode index to map through the method's `LineNumberTable`. Java frames below it still print normally, because each Java frame records its own bytecode index. The undefined pc also connects to safepoints: a thread sitting in native code holds no in-flight JVM frame state, which is why HotSpot can treat it as already safe rather than stopping it.

code

text · 3 lines
text
java.lang.Thread.sleep(Native Method)
  at com.example.Worker.pause(Worker.java:42)
  at com.example.Worker.run(Worker.java:20)

go deeper

for a junior

Know the rule and its visible symptom: native code is not bytecode, so no offset exists and stack traces show Native Method without a line number.

for a middle

Explain the mechanism: bytecode index plus LineNumberTable yields source lines, and native frames have neither, while the caller's frame still holds its own resume point.

for a senior

Add the HotSpot consequence that threads in native state are already safepoint-safe and hold references through JNI handles rather than scanned stack slots.

for a principal

Frame it as a boundary in the execution model: inside the boundary the JVM defines instruction-level semantics, outside it defers to the platform ABI, which is why specification guarantees stop at the JNI edge.

## The rule The JVM specification says: if the method currently being executed by a thread is not native, the pc register contains the address of the JVM instruction currently being executed; if the method is native, the pc register's value is undefined. ## Why undefined rather than something The pc register's type is, in effect, a bytecode address. A native method implemented in C or C++ and bound through JNI has no bytecode array, no offsets, and no JVM instructions. Its execution is driven by the processor's own program counter under the platform ABI. The JVM does not interpret that code, cannot map it to any bytecode index, and does not want to force implementations to maintain a fabricated value, so the specification deliberately leaves it unspecified. This is the same style of rule as the one allowing the JVM stack to be implemented in any way as long as observable behaviour matches. ## What you actually observe Stack traces expose the difference. For a Java frame, the JVM records the bytecode index at the call site and maps it through the method's `LineNumberTable` attribute to a source line, so you get `ClassName.method(File.java:123)`. For a native frame there is no bci to map, so the JVM prints `ClassName.method(Native Method)`. That single line in a stack trace is the practical face of this specification clause. Note that the mapping requires the class to carry line-number information: compiling without debug information removes the table and leaves `Unknown Source` even for Java frames, which is a different cause of a missing line number and worth not confusing with a native frame. ## Crossing the boundary When a Java method calls a native method, the JVM pushes a frame for the native call and, in HotSpot, the thread transitions into a native state. On return, control comes back to the caller's Java frame, which knows its own resume offset, so the thread's Java-side execution point is restored regardless of what happened while the pc register was undefined. If native code calls back into Java through JNI, a fresh Java frame is entered and the pc register becomes meaningful again for that frame. ## Native method stacks The specification also allows an implementation to provide separate native method stacks (historically called C stacks) for native code, with their own `StackOverflowError` and `OutOfMemoryError` conditions. Some JVMs use one stack for both. Either way, the storage for native execution is distinct from the notion of a bytecode pc. ## Safepoints A HotSpot detail worth knowing at senior level: a thread executing native code is considered to be at a safepoint already, because it is not manipulating JVM-managed frame state and the object references it uses are reached through JNI handles rather than raw stack slots. It does not have to be halted for the VM to reach a global safepoint; instead it is blocked on the way back into Java state. The undefined pc and this safepoint treatment come from the same underlying fact: while in native code the thread is outside the JVM's instruction-level model of execution.

  • If the pc register is undefined during a native call, how does the thread know where to continue when the native method returns?
    The resume point of the caller is not kept in the pc register; it is saved in the caller's frame on the JVM stack when the invocation happens. On return, that frame is made current again and its saved bytecode index becomes the pc value once more.
  • You see Unknown Source instead of a line number for a Java frame. Is that the same phenomenon?
    No. That frame does have a bytecode index, but the class was compiled without the LineNumberTable debug attribute, so the JVM cannot map the index to a source line. Native Method means there is no bytecode index at all.

saying these in an interview costs you the question

  • Claiming the pc register stores the native function's machine address; the specification calls it undefined, not repurposed
  • Saying the thread loses its ability to return because the pc is undefined; the return point lives in the caller's frame
  • Confusing Native Method with Unknown Source, which is a missing LineNumberTable
  • Assuming native execution has no stack at all; implementations may provide a separate native method stack

context