The idea: every thread stops itself

Some JVM work needs the heap and every thread's stack to hold still: a GC pause that moves objects, a thread dump, deoptimizing compiled code. The JVM does that work at a safepoint, a moment when no thread runs Java code. The VM thread runs these jobs, called VM operations. But it cannot freeze a running thread from outside. It can only ask, and each thread stops itself the next time its own code checks.

Timeline with four thread lanes. At t = 0 the VM thread arms the polls. T2 in native and T4 blocked are safe at once. T1 stops at its return poll at 15 microseconds. T3 runs a counted loop with no poll and stops only at 180 ms. Then the GC runs for 4 ms. The span from arming to the last thread is Reaching safepoint; the GC work is At safepoint
The pause starts when the polls are armed, not when the GC starts: everyone waits for the slowest thread.

A safepoint runs in four steps, in SafepointSynchronize::begin and end:

  1. The state becomes _synchronizing and the VM thread arms the poll of every Java thread.
  2. Each thread reaches a poll, sees it armed and blocks. The VM thread counts safe threads.
  3. When all are safe the state is _synchronized and the VM operation runs.
  4. The VM thread disarms the polls and wakes the threads.

(Demo 1: young GC, every thread reaches a poll fast.)

Where a thread checks its poll

Since JDK 10 every thread has its own polling word and polling page (thread-local polls, JEP 312), so the VM thread can arm one thread or all of them. Checking costs one instruction, so compiled code checks only at a few places:

WhereWhat the thread does
method returncompares the stack pointer with its polling word; armed means a slow path into the VM
loop back-edgereads its polling page; the armed page is unreadable, so the read faults and the signal handler blocks the thread
in native code (JNI)nothing: it is already safe, because native code cannot touch Java objects directly
blocked, sleeping, waitingnothing: already safe
native → Java transitionchecks the poll; armed means it blocks until the safepoint ends

So the VM thread counts T2 (in socketRead0) and T4 (in Thread.sleep) as safe at once. When T2's read returns at 40 µs it blocks in the transition and never runs Java code during the pause. The interpreter checks at bytecode boundaries. Compiled code is what makes time-to-safepoint long. See how Java code is compiled for C1 and C2.

Counted loops and loop strip mining

A counted loop has an int index and a known bound, like for (int i = 0; i < n; i++). C2 can remove the poll from such a loop, because the loop is sure to end. That makes the loop faster, but a thread in a long one does not check its poll until the loop ends. Every other thread is already stopped and waits for it. This wait is the time-to-safepoint (TTSP). (Demo 2: a counted loop without a poll stalls the GC.)

Left: a counted int loop compiled with no poll inside; the poll is only after the loop, so 180 ms pass before the thread checks. Right: the same loop strip-mined into an inner loop of 1000 iterations with no poll and an outer loop with a poll on its back-edge, so the thread checks about every microsecond
Strip mining keeps the fast inner loop and adds a poll every 1000 iterations.

Since JDK 10, C2 can strip-mine the loop instead: it splits it into an inner loop of -XX:LoopStripMiningIter=1000 iterations with no poll and an outer loop with a poll. This is on by default with G1, ZGC and Shenandoah (-XX:+UseCountedLoopSafepoints). With Parallel GC, or with -XX:-UseCountedLoopSafepoints, the long loop is back. The same stall hits every safepoint operation, not just GC: Demo 3: jstack waits for the same loop.

Reading -Xlog:safepoint

The GC log reports only the GC's own work, so a 184 ms stop shows as Pause Young ... 4.000ms. Run with -Xlog:safepoint to see the whole stop. Each line gives, in nanoseconds:

FieldMeaningDemo 2
Time since lastfrom the end of the last safepoint to the start of this one1 s
Reaching safepointpolls armed → last thread safe (TTSP)180.000 ms
At safepointthe VM operation itself4.000 ms
Totalhow long application threads were stopped184.000 ms

When Reaching safepoint is large, some thread runs a long time without a poll. -XX:+SafepointTimeout -XX:SafepointTimeoutDelay=100 makes HotSpot name the threads that are still not safe after 100 ms. Async profilers can also show where threads were when a safepoint began.

Handshakes: stop one thread

Many jobs need only one thread: its stack trace, deoptimizing its frames, revoking something it holds. A thread-local handshake (JEP 312) arms only that thread's poll. The thread runs the job itself at its next poll and goes on; nobody else stops, and the safepoint state stays _not_synchronized. If the target is in native or blocked, the requesting thread can run the job for it. -Xlog:handshake logs each one. (Demo 4: a handshake stops only T1.)

See also Java garbage collection for what the VM operation does once all threads are stopped, synchronized lock states (biased locking needed a safepoint to revoke a bias, one reason it was removed), how a Java thread runs on a CPU core and the Java Memory Model.

What the page leaves out

The interpreter's own poll (a dispatch table swap); the exact x86 poll instructions and the signal handler; GC work done at the safepoint by parallel worker threads instead of the VM thread; operations that can run without a safepoint (concurrent GC phases of ZGC and Shenandoah, which still use short safepoints or handshakes); the "safepoint bias" of sampling profilers; and how JNI critical regions (GetPrimitiveArrayCritical) can delay a GC.