Safepoints
Purpose
Explain a JVM safepoint interval that the GC operation line does not fully account for. Safepoint Total is
Reaching + At + Leaving on the illustrated JDK 25 build. GC pause timers have their own
boundaries and need not equal At exactly —
but endpoint p99 also includes queueing, blocking and dependencies. Correlation must prove
that a request gap overlaps process-wide loss of progress before calling the residual TTSP.
The second thing this prevents is a fix that changes nothing. The flags most often prescribed for high time-to-safepoint are either already the default (accepted silently, no behaviour change, root cause still undiagnosed) or removed from the JVM entirely. Confirm the default in the target runtime before proposing a flag.
Workflow
- Establish a candidate interval. Align request/thread progress, GC/safepoint events and OS scheduling. A latency value minus summed GC durations is not a valid decomposition when requests overlap, queue or wait on dependencies.
- Enable
-Xlog:safepoint=infowithtimeanduptime, and inspect every safepoint in the window — not just the GC ones.Deoptimize, thread dump, heap dump and class redefinition are safepoints that no GC log mentions. - Split the pause.
Reaching safepointis elapsed synchronization time; a late required thread can dominate it, but coordination and scheduling also contribute.At safepointcovers VM work after synchronization;Leavingcovers release work. A highAtpoints to the VM operation/cleanup rather than TTSP. Correlate matching GC or VM-operation intervals instead of equating their timers or subtracting percentiles. - Name the slow thread when sync time dominates:
-XX:+SafepointTimeout -XX:SafepointTimeoutDelay=<ms>(default 10000) logsThreads which did not reach the safepoint:with each late thread's name and state, at-Xlog:safepointwarning level — no stack (executed, 25.0.3). Get the stack from an async-profiler wall-clock profile over the same window, or, in a test environment only,-XX:+UnlockDiagnosticVMOptions -XX:+AbortVMOnSafepointTimeout, which attempts to write anhs_err; fatal-error stacks can be partial or unavailable. - Classify the cause from aligned evidence — delayed poll in compiled/interpreted/runtime code, transition/critical region, page fault, or a runnable thread not scheduled because of host contention/throttling. A stack sample alone is not causal proof.
- Verify the proposed flag is not already the default in the target binary with
-XX:+PrintFlagsFinal -versionbefore writing it into a recommendation. - Change one cause, then repeat the same measurement with the same procedure.
Rules
- Poll encoding depends on compiler and site. In JDK 25 x86 sources, runtime/interpreter
paths test the thread-local polling word; return polls compare it with a stack pointer.
C2 loop polls also use a thread-local polling address and a memory test: the armed address
points at a protected page and the fault transfers control to HotSpot. Thread-local polling
did not universally remove fault-based polls. Inspect emitted code and port sources; do not
assume L1 residency or assign a universal cycle cost (
reading-jit-assembly). - HotSpot emits polls at selected returns/back-edges and other transition points; optimization can move/elide candidates. Threads in JVM-recognized blocked/native-safe states need not run Java code to acknowledge, but state transitions and OS scheduling still affect timing.
- C2 strip mining is one counted-loop polling strategy. It splits a
counted loop into an outer loop advancing in strips of
-XX:LoopStripMiningIterand an inner loop that runs a whole strip without a poll; the poll sits on the outer back-edge. This bounds that loop's algorithmic poll interval by one strip; descheduling, faults and other runtime regions can still make observed TTSP larger. -XX:+UseCountedLoopSafepointsis collector-dependent, not a JDK-wide default (executed, 25.0.3,-XX:+PrintFlagsFinalper collector): G1, ZGC and Shenandoah set ittruewithLoopStripMiningIter=1000; Parallel and Serial leave itfalsewithLoopStripMiningIter=0. That removes counted-loop strip-mining polls; other checks around the compiled path may remain. Enabling it is a hypothesis with compiler/throughput trade-offs, not an automatic fix. Under the other three it changes no effective default.LoopStripMiningIterShortLoopwas 100 for those three and 0 for Serial/Parallel on this build. It is a C2 short-loop heuristic; inspect effective values and generated code, not a portable guarantee that every counted loop has that exact poll interval.-XX:+UseThreadLocalHandshakeswas removed in JDK 15. Passing it producesUnrecognized VM optionand the JVM does not start (executed, 25.0.3).RevokeBiasdoes not exist on a JDK 18+ runtime. Biased locking was disabled by default in JDK 15 (JEP 374) and the code removed in JDK 18 (JDK-8256425) — two different dates, routinely conflated.RevokeBiasin a log means the log came from an older JVM.-XX:GuaranteedSafepointIntervalchanged from a 1000 ms default to0in JDK 23 and is a diagnostic flag on 25 — setting it without-XX:+UnlockDiagnosticVMOptionsrefuses to start (executed). A service migrated from an older JDK will show a different periodic safepoint pattern; that alone is not a regression.- Global safepoint versus handshake: observed JDK 25 GC pauses, heap inspection and thread-dump
(
jstack,jcmd Thread.print,ThreadMXBean.dumpAllThreads) and JVMTI class operations can stop every thread. Single-thread stack sampling (Thread.getStackTrace(), the JFR sampler), per-thread deoptimisation, concurrent-collector thread-root scanning use handshakes in the listed implementation paths and leave unrelated threads running.-Xlog:handshake=infonames each one; the table is inreferences/instrumentation.md. jcmd Thread.dump_to_fileis a different dump introduced with JEP 444: it avoids a global application pause and has different contents/consistency fromThread.print. Do not group all thread-dump commands under the same safepoint cost.- A thread executing ordinary JNI/FFM native code is normally in a safepoint-safe native state; it does not have to return before a global safepoint can proceed. The transition back to Java checks synchronization. JNI critical regions, VM/native transitions and runtime stubs have different constraints and must be identified explicitly; do not “fix” ordinary native batches for TTSP.
- Repeated global thread dumps can perturb production and form a biased statistical sampler.
Use a wall/CPU sampler appropriate to the question and quantify its loss/overhead. Current
async-profiler uses
asprof; JFR CPU-Time Profiling (JEP 509) is experimental and Linux-only on the JDK 25 baseline. -Xlog:safepointis JDK 9 unified logging (JEP 158), not a JDK 17 feature.-XX:+PrintSafepointStatisticswas deprecated in JDK 11 — where it starts and warns — and is anUnrecognized VM optionfrom 17 onward, so a runbook still carrying it fails at launch rather than degrading. Its output lives on as-Xlog:safepoint+stats=debug.- On the tested HotSpot build,
Thread.yield()reaches a runtime path with a safepoint check; this is not a Java API guarantee.Thread.sleep(0)andThread.onSpinWait()must not be used as correctness mechanisms for safepoint responsiveness.
Validation and operational constraints
- Derive timeout thresholds from the service SLO and normal TTSP distribution; overly low
SafepointTimeoutDelaycan flood diagnostics, while abort-on-timeout is test/canary only. - Test long compiled loops, CPU throttling/descheduling and relevant JNI critical paths separately. Validate both TTSP and throughput after any code/compiler change.
- Record build, collector, compiler tier/effective flags and logging/JFR loss. None of these mechanics is a Java-language portability guarantee.
- Return the aligned interval, observed timing fields, candidate late thread, supporting evidence and proposed validation. Missing events/stacks leave attribution unresolved; do not infer absent pauses from a configuration that did not record them.
References
- Instrumentation and log fields — the exact JDK 25
-Xlog:safepointline format and what each field means, whatSafepointTimeoutdoes and does not print, the JFR safepoint events with their real field names, and the handshake-versus-safepoint table with-Xlog:handshake. Read before enabling logging, writing an analysis over a JFR recording, or deciding whether an operation stops the world. - TTSP triage — the triage tree from "latency exceeds the GC log" to a named cause, the TTSP-by-thread-state table, and the cause-to-strategy table with each trade-off. Read once sync time is confirmed to dominate and the cause is still unidentified.
Authoritative sources: JEP 312: Thread-Local Handshakes, JEP 376: ZGC Concurrent Thread-Stack Processing, JEP 518: JFR Cooperative Sampling, and JEP 158: Unified JVM Logging. For poll encodings, see JDK 25 x86 C2 safepoint node, poll-word/return helpers, and polling page setup.