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.
A safepoint runs in four steps, in SafepointSynchronize::begin and end:
- The state becomes
_synchronizingand the VM thread arms the poll of every Java thread. - Each thread reaches a poll, sees it armed and blocks. The VM thread counts safe threads.
- When all are safe the state is
_synchronizedand the VM operation runs. - 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:
| Where | What the thread does |
|---|---|
| method return | compares the stack pointer with its polling word; armed means a slow path into the VM |
| loop back-edge | reads 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, waiting | nothing: already safe |
| native → Java transition | checks 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.)
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:
| Field | Meaning | Demo 2 |
|---|---|---|
Time since last | from the end of the last safepoint to the start of this one | 1 s |
Reaching safepoint | polls armed → last thread safe (TTSP) | 180.000 ms |
At safepoint | the VM operation itself | 4.000 ms |
Total | how long application threads were stopped | 184.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.
See where this page fits on the Java & the JVM mind map.