Contents

Pinned virtual threads

Writen by: David Vlijmincx

Introduction

Virtual threads are very good at waiting while being blocked. Because the carrier thread will pick up/ mount a new virtual thread to run. There are cases when it doesn't happen and a virtual thread is not unmounted while it is blocked. When a virtual thread is blocked and stuck on a carrier thread, we call that virtual thread pinned to its carrier thread. Virtual threads that are planned hurt your scalability because those carrier threads cant pick up other virtual threads.

A virtual thread gets pinned when it encounters a method that does not support the unmounting and mounting process like some file IO operation or JNI calls. In this post, we look into how you can detect virtual threads that are pinned using two command line options.

Run options

You can use the following two run options to set the amount of information you want to see when a virtual thread gets pinned.

1
2
3
-Djdk.tracePinnedThreads=full

-Djdk.tracePinnedThreads=short

System properties

You can also set these two system properties if you prefer to use these instead of run options.

1
2
3
jdk.tracePinnedThreads=full

jdk.tracePinnedThreads=short

Full or short?

You can set the trace to either full or short. I added two examples of traces to give you an idea of what they look like. The first example shows you the full trace.

full trace:

1
2
3
4
5
6
7
8
9
Thread[#34,ForkJoinPool-1-worker-2,5,CarrierThreads]
    java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:185)
    java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393)
    java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:631)
    java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:803)
    java.base/java.lang.Thread.sleep(Thread.java:507)
    org.example.Main.test(Main.java:24) <== monitors:1
    org.example.Main.lambda$main$1(Main.java:11)
    java.base/java.lang.VirtualThread.run(VirtualThread.java:311)

If you choose to use the short options, it will just show you the problematic frames.

Short trace:

1
2
Thread[#32,ForkJoinPool-1-worker-1,5,CarrierThreads]
    org.example.Main.test(Main.java:27) <== monitors:1

Example code

To create these examples, I used the following code. At the time of writing the synchronized method causes the virtual threads to get pinned.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
    public static void main(String[] args) throws InterruptedException {

        Main main = new Main();

        Thread thread = Thread.startVirtualThread(() -> main.test(0));
        Thread thread1 =Thread.startVirtualThread(() -> main.test(1));
        Thread thread2 =Thread.startVirtualThread(() -> main.test(2));

        thread.join();
        thread1.join();
        thread2.join();

    }

    synchronized public void test(int i) {

        System.out.println("i = " + i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

Conclusion

In this post, we looked at what pinned virtual threads are and how you can detect them using run options.

Further reading