kms333 kms333 - 7 months ago 27
Java Question

Java thread executing remainder opearation blocks all other threads

The following code snippet executes two threads, one is a simple timer logging every second, the second is an infinite loop that executes a remainder operation:

public class TestBlockingThread {
private static final Logger LOGGER = LoggerFactory.getLogger(TestBlockingThread.class);

public static final void main(String[] args) throws InterruptedException {
Runnable task = () -> {
int i = 0;
while (true) {
if (i != 0) {
boolean b = 1 % i == 0;

new Thread(new LogTimer()).start();
new Thread(task).start();

public static class LogTimer implements Runnable {
public void run() {
while (true) {
long start = System.currentTimeMillis();
try {
} catch (InterruptedException e) {
// do nothing
}"timeElapsed={}", System.currentTimeMillis() - start);

This gives the following result:

[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=13331
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1006
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004

I don't understand why the infinite task blocks all other threads for 13.3 seconds. I tried to change thread priorities and other settings, nothing worked.

If you have any suggestions to fix this (including tweaking OS context switching settings) please let me know.


After all the explanations here (thanks to Peter Lawrey) we found that the main source of this pause is that safepoint inside the loop is reached rather rarely so it takes a long time to stop all threads for JIT-compiled code replacement.

But I decided to go deeper and find why safepoint is reached rarely. I found it a bit confusing why the back jump of while loop is not "safe" in this case.

So I summon -XX:+PrintAssembly in all its glory to help

-XX:+UnlockDiagnosticVMOptions \
-XX:+TraceClassLoading \
-XX:+DebugNonSafepoints \
-XX:+PrintCompilation \
-XX:+PrintGCDetails \
-XX:+PrintStubCode \
-XX:+PrintAssembly \

After some investigation I found that after third recompilation of lambda C2 compiler for some reason threw away safepoint polls inside loop completely. It seems like it's a form of JDK-8144935 bug.

But there is a workaround. You can try to use -XX:+UseCountedLoopSafepoints (it will cause overall performance penalty). After using it C2 compiler continue keeping safepoints at the back jumps and original pause disappears completely.

Or you can rewrite your code by adding safepoint manually. For example Thread.yield() call at the end of cycle will also fix pause.


There is a great must-read blog post "Safepoints: Meaning, Side Effects and Overheads" by Nitsan Wakart covering safepoints and this particular issue.