Compilation and Inlining Logs
Purpose
Read the compiler's own output instead of guessing at it. Three diagnoses look alike from the outside and lead to three different flags: the method never became native code at all; a specific call inside a tree that compiled fine was not inlined; or code that was already generated got invalidated afterwards. Confusing them is how a session ends with the wrong flag changed.
The failure this prevents is the plausible-looking command that silently answers nothing —
a tier filter on the wrong field, -Xlog:jit with no sub-tag, a JFR recording whose
threshold drops every compilation, a directive that only applies to compilations that have
not happened yet — and is then read as "there is nothing to see".
Workflow
- Decide which question you are asking, because it picks the tool. When and at which
tier a method became native code is
PrintCompilationor-Xlog:jit+compilation; which calls inside one compilation tree were inlined isPrintInliningor-Xlog:jit+inlining=debug; currently listed nmethods and their tier/state are visible throughjcmd <pid> Compiler.codelist; continuous production monitoring is JFR. A method can execute inlined in several callers without its own listed nmethod. - Record the runtime and compilation mode before interpreting a tier. On the usual
server HotSpot with tiered compilation enabled, levels 1–3 are C1 modes and level 4 is C2.
Under
-XX:-TieredCompilationthe tier column is structurally absent; JVMCI compilers, compiler-only builds, and vendor runtimes can require a different interpretation. InspectTieredStopAtLevel, runtime vendor/build, effective flags/directives and the capture start time; absence of tier 4 can be intentional. Examples use HotSpot 25.0.3, not a portable Java API contract. Preserve the target project/toolchain rather than upgrading to fit a flag. - Parse the line structurally. Timestamp, compile id, a five-position flag field, tier,
Class::method (bytes), optional status. A blank flag field emits no token, so a whitespace-split field index is wrong for most lines. Seereferences/printcompilation-format.md. - Walk the method down the tree: absent at every tier, present at tier 1 after a
COMPILE SKIPPED:line, stuck at tier 3, returning withmade not entrant: uncommon trap, or at tier 4 with the hot path still slow — each suggests a different next check. A one-offmade not entrant: not usedcommonly accompanies promotion; repeated invalidation still needs correlation with recompilation, class loading, directives, and deoptimization evidence. Inlining matters at any active compiler tier; interpret C2 verdicts only when C2 is the relevant target and a profile implicates this call. - Read the verdict on the tier-4 tree, not the tier-3 one above it. C2 names the limit
it applied —
too big,hot method too big,inlining too deep,virtual call,already compiled into a big method— andcallee is too largeis C1's verdict, which says nothing about C2. Seereferences/inlining-diagnosis.md. - Refactor the common path to fit rather than raising a global limit. A bigger
FreqInlineSizeapplies to every call in the process and can cost aggregate throughput through code bloat while "fixing" the target method. - Steer one method, not the process. A compiler directive or
CompileCommandscoped to a caller affects matching compilation tasks; calleeCompileCommandpatterns may affect many callers. On the examined HotSpot implementation, tasks capture directives when initialized, so already queued tasks can retain old policy. Seereferences/directives-and-production-logging.md. - Confirm the fix in a controlled comparison — that the call is now inlined or the method now compiles, and that workload-level latency/throughput did not regress. Remove session-only flags; retain bounded production logging only when its operational value and cost are established.
Rules
PrintCompilationis a product flag.PrintInlining,LogCompilation,CompilerDirectivesFileand-XX:CompileCommand=PrintInlining,…all need-XX:+UnlockDiagnosticVMOptionsbefore them on the command line; the JVM refuses to start otherwise (executed, 25.0.3).- The unified-logging equivalents exist and need no unlock:
-Xlog:jit+compilationprints the same lines asPrintCompilation(minus the timestamp column, plus decorations) and-Xlog:jit+inlining=debugprints the same trees asPrintInlining. Plain-Xlog:jitmatches no tag set and warnsNo tag set matches selection: jit. Both can be turned on in a running JVM withjcmd <pid> VM.log what=jit+compilation output=<file>. - Filter by structure, not by field index:
awk '$4 == 4'matches only lines that carry exactly one flag character — 6 of 21 tier-4 lines in a small run (executed, 25.0.3). Usegrep -E '^ *[0-9]+ +[0-9]+ [ %s!bn]{5} 4 ', and validate any extraction command against real output before it enters a script. - The flag field has five fixed positions:
%OSR,ssynchronized,!exception handler,bblocking,nnative wrapper. A native wrapper prints tier0and(native), not a byte count. None of the positions is the tier. made zombieno longer exists in JDK 20+ because JDK-8290025 removed the sweeper and zombie state. Interpretmade not entrantby rate, reason, and successor compilation; do not alert on a reason string alone.DontCompileHugeMethodsdefaults to true andHugeMethodLimitto 8000 bytecodes on current OpenJDK, but this is an implementation policy, not a JVM specification. JDK 17–25 have JDK-8366118: the guard can be bypassed with-XX:-TieredCompilation; JDK 26 fixes it. A huge method can therefore be absent from compilation events, explicitly rejected, or—on affected non-tiered runtimes—compiled. Confirm flags, mode, version, and bytecode size.- C2 names the limit in the verdict on JDK 25 (
bytecodeInfo.cpp); the generictoo largestring exists only as C1'scallee is too large. Grep for the exact strings your build prints, and read the tier of the line the tree hangs from first. - On the examined JDK 25 server build, a callee below
MaxInlineSize(default 35 bytecodes) does not need a hot-site allowance, but it is never guaranteed to inline.virtual callorno static binding(polymorphic, megamorphic, unresolved, or insufficiently profiled receiver),inlining too deep,disallowed by CompileCommand,not inlineableafter(not loaded), oralready compiled into a big methodall still refuse an 8-byte callee. -XX:CompileThresholdis honoured only under-XX:-TieredCompilation. Under the default it is accepted and ignored. The real thresholds are per tier (Tier3InvocationThreshold,Tier4InvocationThreshold); read them with-XX:+PrintFlagsFinal, and read the live counters with-XX:+PrintTieredEvents.- Current HotSpot JFR does expose inlining:
jdk.CompilerInliningcarries caller, callee,bci,succeededand the same verdictmessagefor an inlining attempt; one site can yield multiple events. CorrelatecompileIdwith the compilation's compiler/tier and preserve nested context. It is disabled in bothdefaultandprofile, andjdk.Compilationhas a threshold of 1000 ms (default) or 100 ms (profile) on the examined JDK 25 configuration, which filters most ordinary compilations. Inspect the configuration shipped by the runtime and enable them explicitly:jdk.CompilerInlining#enabled=true,jdk.Compilation#threshold=0ms. - The current failure event is
jdk.CompilationFailurewithfailureMessage; its default enablement is recording-configuration specific. Do not substitute the plausible but wrong namejdk.CompilerFailure; confirm names and fields withjfr metadataon the target runtime. - A startup
-XX:CompileCommand=excludeprevents matching top-level compilation and inlining;compileonlyrestricts the compilation set. These are high-risk compiler controls, appropriate mainly for diagnosis or a scoped compiler-bug mitigation—not general tuning. Confirm the live directive stack because runtime directives can change future compilation policy. - A directive with an
inlinelist replacesCompileCommand=inline/dontinlinefor that caller; an option a directive sets explicitly beats the sameCompileCommandoption; the first matching directive from the top of the stack wins, so ajcmdaddition shadows the file. None of it touches code that is already compiled. PrintInliningandLogCompilationare session tools;-Xlog:jit+compilationto a rotated file is the one that can stay on. Volume and overhead are inreferences/directives-and-production-logging.md.- "Compiled" is not "optimised". A hot method sitting at tier 1 or tier 3 is compiled and
may benefit from further compilation, but tier alone proves no performance deficit. A matching
tier-1 retry after a tier-4
COMPILE SKIPPED:documents a C2 bailout, not every tier-1 method. - Use JMH for isolated causal experiments and a representative workload for the engineering
decision.
@CompilerControlcan stabilize a particular experiment but creates an artificial compilation policy; confirm the final unforced code.System.nanoTime()around one loop mixes interpreter, C1, C2, OSR, and harness effects.
References
Deliver the target runtime/mode, capture interval/settings, caller and compilation identity, observed verdict versus causal hypothesis, and the smallest controlled next experiment. With missing profiles or filtered/partial logs, state what remains unknown; do not infer no compilation or a performance fix from absence alone. Record measured workload outcomes and instrumentation cost separately from the changed compiler decision.
- The PrintCompilation format — the columns, the
five flag positions, the status suffixes on JDK 25, the
-Xlog:jit+compilationform, what changes without tiered compilation, and filtering commands that survive a blank flag field and a wide compile id. Read before parsing or scripting against compilation output. - Diagnosing an inlining refusal — the verdict strings C1
and C2 print, the three-band size model, the refusal categories and what to do about each,
the escalation order for changing limits,
jdk.CompilerInlining, and theLogCompilationXML that JITWatch reads. Read when a specific call was not inlined and you need to know why. - Directives and production logging —
CompileCommandand the directives file side by side, match syntax, precedence, thejcmdlifecycle, per-flag volume and overhead, the JFR events and their default thresholds, and a symptom-to-cause table. Read before steering a compilation or before enabling any of these in an environment that matters. - JEP 165: Compiler Control
- JEP 158: Unified JVM Logging
- JDK 25
javalauncher options - JDK-8366118:
DontCompileHugeMethodsand non-tiered compilation