A `-XX:+PrintCompilation` log shows a compile tagged with a `%` symbol — an on-stack replacement compile — right after a batch job's main loop has been running for a while. Why does the JVM need a special mechanism for this, instead of just letting the normal tier-based recompilation handle it the way it does for an ordinary method?
Because normal compiled-code replacement only takes effect at a method's entry point — the interpreter finishes running the current invocation, and only the next call goes to compiled code. That works fine for a method called millions of times with each call being short. It completely fails for a method with one long-running loop that never returns during a single invocation — a batch job's main loop, a stream consumer draining a large queue — because the interpreter would keep executing that one call, slowly, for the loop's entire duration, no matter how much faster compiled code sits ready and unused right beside it. On-stack replacement (OSR) exists specifically to fix that: the JVM compiles a version of the method starting from the loop's back-edge rather than its entry, and mid-loop, at a safe point, swaps the interpreter's live stack frame for a compiled one — reconstructing every live local variable's value in the new frame's layout — so the loop already in progress starts running compiled code without waiting for the method to be called again.