Deoptimization
Purpose
Decide whether a deoptimisation is the JIT working correctly or a method that will never reach stable optimised code. Speculation is what makes C2 fast: it bets on a profiled assumption, embeds a check, and unwinds without ever producing a wrong result when the bet fails. The trap is recorded in method profiling data and can change later compilation decisions. A bounded burst that decays after recompilation is the design working; continued events at the same site require diagnosis rather than an assumption that all deoptimisation is benign.
The failure this prevents is both directions of the same mistake — alerting on every
jdk.Deoptimization event until the alert is ignored, and raising a recompilation cutoff so
the JVM takes longer to give up on a method whose underlying assumption keeps changing.
Workflow
- Establish whether the question is one method or a post-deploy pattern, and fix a reference window (a deploy, a config change, a library or plugin rollout) to correlate against. Inspect the deployed vendor/build, compiler mode and flags alongside project toolchains; JDK 25 observations are not authorization to upgrade the target or enable flags.
- Collect the reason, the action and the compile id, not just the fact. JFR
jdk.Deoptimization(enabled in the baselinedefault.jfc; stacks enabled byprofile.jfcor explicit event settings) for production,-Xlog:deoptimization=debugfor a session. Both see uncommon traps only: a class-loading orRedefineClassesinvalidation appears in neither, so collect-Xlog:jit+compilation=debugand-Xlog:dependencies=debugalongside. Seereferences/deopt-tooling.md. - Group by method and bci, reason and action, over a stated window. The criterion is
the rate per site and its decay, not presence: a site trapping once, or up to four times
with
maybe_recompile, then going quiet is converging. A site emittingnoneat a steady rate needs its compilation history checked — the action preserves compiled code and does not update trap state, but the action alone does not identify why it was emitted. - For a
class_check, decide which of the two routes it took. Trap lines and events withinstruction = invokeinterfaceat onecidare a per-invocation guard. Several unrelated methodsmade not entrant: marked for deoptimizationin the same millisecond, right after aclass+loadline, are a CHA dependency invalidated by class loading — no application bytecode ran to trigger it, and no trap line or JFR event exists for it. - Attack the assumption, not the threshold. A static type that cannot gain implementors
at the call site, warm-up that exercises every expected type, loading generated classes
before traffic. See
references/reasons-and-actions.mdfor the reason-to-fix table andreferences/production-patterns.mdfor the levers and what each costs. - Validate mechanism and service outcome together. Check the target site's rate and compilation state under comparable workload, then CPU, allocation and latency guardrails. Eliminating events by disabling compilation can worsen performance. Restore temporary diagnostics to their prior settings; retain intentionally configured monitoring.
Rules
- The correct log invocation is
-Xlog:deoptimization=debug:file=deopt.log:time,uptime.jit+deoptimizationis not a tag set: the JVM printsNo tag set matches selectionand starts anyway;infoemits nothing;traceadds nothing overdebug(executed, 25.0.3). Verify tag acceptance and collection coverage. An empty file can also mean no traps occurred; do not force a production trap merely to make it non-empty. jdk.Deoptimizationexists since JDK 14 (JDK-8216041). Fields:compileId,compiler,method,lineNumber,bci,instruction,reason,action,eventThread,stackTrace. There is notopFrame; asking for one throwsIllegalArgumentException.reasonandactionanswer different questions.reasonis the cause;actionis the runtime's response, and there are five:none,maybe_recompile,reinterpret,make_not_entrant,make_not_compilable. The last three request invalidation;maybe_recompilecan also invalidate after sufficient traps. All five deoptimise the frame that hit the trap.- Reason names come from
_trap_reason_name[]indeoptimization.cpp, not from any specification. On the tested JVMCI-enabled Temurin 25.0.3 build, three are suffixed:intrinsic_or_type_checked_inlining,bimorphic_or_optimized_type_check,null_assert_or_unreached0. Confirm any name a script matches against a real collection. - Recorded trap history can suppress the same speculation at a bci through
Compile::too_many_traps. The prior oscillating-branch experiment produced oneunstable_if, but concurrent frames, compiled versions, missing/replaced profiles and actionnoneprevent a universal one-event bound. Do not split anifmerely because its first trap appeared. - The observed defaults on Temurin 25.0.3 are
PerBytecodeTrapLimit=4,PerMethodTrapLimit=100,PerMethodSpecTrapLimit=5000(experimental),PerBytecodeRecompilationCutoff=200,PerMethodRecompilationCutoff=400. C2 stops recompiling — emitting traps with actionnone— once a method has decompiledPerMethodRecompilationCutoff/2+1= 201 times or a bci has 25 overflow recompiles under those defaults. These are HotSpot implementation details, not Java contracts; verify flags and source on the deployed build. A sustained same-site storm is the signal to investigate, not a magic count copied from this baseline. - C2 recompilation-cutoff exclusion is at C2 level in the baseline.
PrintCompilationprintsmade not compilable on level 4 … give up compilingand the method is recompiled by C1 — tier 1, no profiling — in the prior tiered experiment (Compiler.codelist). C1 fallback requires enabled/available C1 and policy scheduling; inspect live code instead of assuming it. Treat the exclusion as lasting for that loaded method; redefinition/reloading and another JVM release can alter the lifecycle. made zombieno longer exists (JDK 20, JDK-8290025). JDK 25 prints the reason aftermade not entrant:—not usedandOSR invalidation of lower levelare tier promotion,uncommon trapis a trap,marked for deoptimizationis a dependency invalidation.- A CHA invalidation on JDK 25 runs as a
Handshake "Deoptimize"(-Xlog:handshake=info;DeoptimizeMarkedClosure,deoptimization.cpp), not a global safepoint.RedefineClassesis a global safepoint and flushes every nmethod with anevol_methoddependency on the class — callers and inliners alike.safepointsowns the cost model of each. - Linking another lambda implementation can load a hidden class and invalidate a still-valid
unique-implementor dependency (observed in the prior lab). Re-evaluating one lambda expression
does not imply a new class each time. Proxies, generated accessors and scripting can do the same;
jvm-class-loadingcovers where they come from. - A mutable feature flag with stable retained profiling usually converges to a real
branch — the lost constant folding, not recurring deoptimisation. A flag that swaps the
type at a hot call site costs the inline tree: monomorphic to bimorphic to a virtual
call (
jit-inlining-and-escape-analysis). Put the choice one level up. - Do not confuse
jdk.CompilationFailure(that compilation attempt failed) withjdk.Deoptimization(an active compiled frame was deoptimised; its nmethod may remain usable). Both on one method require failure text, compile IDs and timing before diagnosing complexity. - Budget scalar replacement into the cost: eliminated objects needed by reconstructed live state may be rematerialised on
the heap during frame reconstruction (
realloc_objects,deoptimization.cpp). A reason to care about recurrence, not to disableEliminateAllocations. -XX:+TraceDeoptimizationisdiagnosticsince JDK 18 (JDK-8154011) and needs-XX:+UnlockDiagnosticVMOptionsbefore it; it prints oneVFrameper inlined level and is a one-off deep session, not a default continuous-production setting. Do not describe a proposed or mainline change as released behavior; inspect the target JDK'sjava -Xlog:helpand flags.
References
All numeric thresholds and output shapes in these references are JDK 25 HotSpot observations. Confirm them against the exact vendor build before automation or production tuning.
- Reasons, actions and mitigations — the reason and action tables with the strings JDK 25 prints and the action observed for each, why a method converges and the two ways it fails to, the two routes into a class-loading deoptimisation with their evidence, the symptom-to-cause table, and the mitigations. Read when you have a reason code and need to decide what it means and what to change.
- Deoptimisation tooling — which tool sees which kind of
deoptimisation, the exact log lines and JFR fields,
PrintCompilationreasons,Compiler.codelistfor the live process, the limit flags with where each is enforced, and correlation with latency spikes. Read before instrumenting a process or writing a script against deoptimisation data. - Production patterns and decisions — the post-deploy timeline and the rate-to-floor criterion, what a restart clears, the sources of runtime class loading, agents, feature flags, the act-or-accept table and the levers with their trade-offs. Read when the question is fleet behaviour after a deploy, or which change to propose.