expertJIT
A JIT compiled a method assuming a value was always an integer, and then a string arrives. What has to happen?
Whether the candidate understands speculation as a two-part mechanism — the assumption and the escape route — and can describe the state reconstruction. This is the question that separates people who have used a JIT from people who have worked on one.
What a strong answer covers
- The compiled code must have a guard: an explicit check, cheap in the common case, that the assumption still holds. When it fails, the method cannot simply continue, because the optimized code was built on the assumption — values may be unboxed, the type check may have been hoisted out of a loop, intermediate operations may have been folded away.
- So it deoptimizes. The runtime takes the machine state at the guard, consults a side table the compiler emitted mapping this program point back to the interpreter's view — which interpreter local corresponds to which register or stack slot, which values are constants, which frames were inlined — and materializes an interpreter frame, or several, since an inlined call chain must be rebuilt as separate frames. Execution resumes in the interpreter at the corresponding bytecode index.
- The compiler also records why. The optimized code is discarded or the guard is marked, and when the method is recompiled the failed assumption is not made again — otherwise the system deoptimizes in a loop, recompiling and falling back forever, which is a real production failure mode that presents as a method that never gets fast.
- The same machinery runs in the other direction for on-stack replacement: a long-running loop already executing in the interpreter is transferred into optimized code mid-iteration, which needs the inverse mapping. And the cost of all of it is paid at compile time — every safepoint where a deoptimization can happen constrains what the optimizer may do, because the state must remain reconstructible.
✓ Green flags
- Names the guard as a separate thing from the speculation.
- Describes state reconstruction concretely, including rebuilding inlined frames.
- Knows the profile is updated so the same speculation is not repeated.
- Mentions on-stack replacement as the dual.
- Notes that deoptimization support constrains the optimizer, which is a cost people forget.
✗ Red flags
- "It throws an exception and falls back to the interpreter permanently." Deoptimization is not an error path, and the method is normally recompiled.
- "The JIT re-runs the method from the start in the interpreter." Side effects already happened; it resumes at the exact bytecode index.
- "You avoid this by writing monomorphic code." Good advice and not an answer to the mechanism question.
- "Guards are expensive, which is why JITs are slow to warm up." Guards are cheap by design; warmup cost is profiling and compilation.
Follow-up
A method in your service is compiled and deoptimized thousands of times per minute. What would cause that, and how would you see it?
Implementation challenge
What to ask them to write or trace on a whiteboard.
Design the side table: for one safepoint in an optimized method that inlined two calls, list exactly what has to be recorded to rebuild the interpreter state.