Java25 中虚拟线程仍然会被 pin 住的 3 种情况
Java juc About 10,415 wordsJEP 491
在JEP 491中解决了虚拟线程在synchronized锁内遇到阻塞时不能从载体线程上卸载的问题后,还列举了3种还没解决的场景。
但这些场景发生的概率很小,且都是编码可控。
解析类符号引用时因类加载阻塞
When resolving a symbolic reference (JVMS §5.4.3) to a class or interface and the virtual thread blocks while loading a class. This is a case where the virtual thread pins the carrier due to a native frame on the stack.
public class BlockingLoadClass {
static {
System.out.println("block load static code begin");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
System.out.println("block load static code end");
}
public static void test() {
System.out.println("test method");
}
}
public class JavaVirtualThreadDemo {
public static void main(String[] args) {
Thread.startVirtualThread(() -> {
System.out.println("invoke static method");
BlockingLoadClass.test();
});
LockSupport.park();
}
}
JFR查得Pinned Reason:
VM call to com.center.BlockingLoadClass.
on stack
PS \openjdk-25> jcmd 18480 JFR.view cell-height=20 jdk.VirtualThreadPinned
18480:
Virtual Thread Pinned
Start Time Duration Event Thread Stack Trace Blocking Operation Pinned Reason Carrier Thread
---------- -------- ------------ ---------------------- ------------------ ---------------------- ----------------------
20:21:51 20.9 s com.center.Blocking... LockSupport.park VM call to com.center. ForkJoinPool-1-worker-
com.center.JavaVirt... BlockingLoadClass.<cli 1
com.center.JavaVirt... nit> on stack
java.lang.Thread.ru...
java.lang.Thread.sl...
java.lang.Thread.sl...
java.lang.VirtualTh...
java.lang.VirtualTh...
java.lang.VirtualTh...
java.lang.VirtualTh...
java.lang.VirtualTh...
java.lang.VirtualTh...
jdk.internal.vm.Con...
jdk.internal.vm.Con...
jdk.internal.vm.Con...
Timespan: 20:12:15 - 20:22:15
类初始化器内阻塞
When blocking inside a class initializer. This is also a case where the virtual thread pins the carrier due to a native frame on the stack.
public class BlockingInitializationClass {
private static final Integer VALUE = createValue();
private static Integer createValue() {
System.out.println("block load static code begin");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
System.out.println("block load static code end");
return 1;
}
public static Integer getValue() {
System.out.println("test method");
return VALUE;
}
}
public class JavaVirtualThreadDemo {
public static void main(String[] args) {
Thread.startVirtualThread(() -> {
System.out.println("invoke static method");
BlockingInitializationClass.getValue();
});
LockSupport.park();
}
}
JFR查得Pinned Reason:
VM call to com.center.BlockingInitializationClass.
on stack
PS \openjdk-25\bin> jcmd 18628 JFR.view cell-height=20 jdk.VirtualThreadPinned
18628:
Virtual Thread Pinned
Start Time Duration Event Thread Stack Trace Blocking Operation Pinned Reason Carrier Thread
---------- -------- ------------ ---------------------- ------------------ ---------------------- ----------------------
20:24:51 10.4 s com.center.Blocking... LockSupport.park VM call to com.center. ForkJoinPool-1-worker-
com.center.Blocking... BlockingInitialization 1
com.center.JavaVirt... Class.<clinit> on stac
com.center.JavaVirt... k
java.lang.Thread.ru...
java.lang.Thread.sl...
java.lang.Thread.sl...
java.lang.VirtualTh...
java.lang.VirtualTh...
java.lang.VirtualTh...
java.lang.VirtualTh...
java.lang.VirtualTh...
java.lang.VirtualTh...
jdk.internal.vm.Con...
jdk.internal.vm.Con...
jdk.internal.vm.Con...
Timespan: 20:17:55 - 20:27:55
等待其他线程完成类初始化
When waiting for a class to be initialized by another thread (JVMS §5.5). This is a special case where the virtual thread blocks in the JVM, thus pinning the carrier.
public class BlockingLoadClass {
static {
System.out.println("block load static code begin " + Thread.currentThread());
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
}
System.out.println("block load static code end " + Thread.currentThread());
}
public static void test() {
System.out.println("test method: " + Thread.currentThread());
}
}
public class JavaVirtualThreadDemo {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println(LocalDateTime.now() + " platform thread invoke static method");
BlockingLoadClass.test();
}).start();
Thread.sleep(10000);
Thread.startVirtualThread(() -> {
System.out.println(LocalDateTime.now() + " virtual thread invoke static method");
BlockingLoadClass.test();
});
LockSupport.park();
}
}
JFR查得Pinned Reason:
Waited for initialization of com.center.BlockingLoadClass by another thread
PS \openjdk-25> jcmd.exe 12420 JFR.view cell-height=20 jdk.VirtualThreadPinned
12420:
Virtual Thread Pinned
Start Time Duration Event Thread Stack Trace Blocking Operation Pinned Reason Carrier Thread
---------- -------- ------------ ---------------------- ------------------ ---------------------- ----------------------
20:16:49 10.4 s com.center.JavaVirt... Object.wait Waited for initializat ForkJoinPool-1-worker-
com.center.JavaVirt... ion of com.center.Bloc 1
java.lang.Thread.ru... kingLoadClass by anoth
java.lang.VirtualTh... er thread
java.lang.VirtualTh...
jdk.internal.vm.Con...
jdk.internal.vm.Con...
Timespan: 20:08:38 - 20:18:38
线程情况
工作线程挂载了虚拟线程,一直没有卸载。
可以看到jcmd命令的Thread.print功能中,会标识Carrying携带的虚拟线程、Mounted挂载的虚拟线程。
"ForkJoinPool-1-worker-1" #27 [5404] daemon prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x000001eb4e2e72d0 [0x0000001cf4afd000]
Carrying virtual thread #26
at jdk.internal.vm.Continuation.run(java.base@25/Continuation.java:251)
at java.lang.VirtualThread.runContinuation(java.base@25/VirtualThread.java:293)
at java.lang.VirtualThread$$Lambda/0x000000002700b890.run(java.base@25/Unknown Source)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(java.base@25/ForkJoinTask.java:1750)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(java.base@25/ForkJoinTask.java:1742)
at java.util.concurrent.ForkJoinTask$InterruptibleTask.exec(java.base@25/ForkJoinTask.java:1659)
at java.util.concurrent.ForkJoinTask.doExec(java.base@25/ForkJoinTask.java:511)
at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(java.base@25/ForkJoinPool.java:1450)
at java.util.concurrent.ForkJoinPool.runWorker(java.base@25/ForkJoinPool.java:2019)
at java.util.concurrent.ForkJoinWorkerThread.run(java.base@25/ForkJoinWorkerThread.java:187)
Mounted virtual thread #26
at jdk.internal.misc.Unsafe.park(java.base@25/Native Method)
at java.lang.VirtualThread.parkOnCarrierThread(java.base@25/VirtualThread.java:813)
at java.lang.VirtualThread.park(java.base@25/VirtualThread.java:751)
at java.lang.System$1.parkVirtualThread(java.base@25/System.java:2284)
at java.util.concurrent.locks.LockSupport.park(java.base@25/LockSupport.java:367)
at com.center.BlockingLoadClass.<clinit>(BlockingLoadClass.java:9)
at com.center.JavaVirtualThreadDemo.lambda$main$0(JavaVirtualThreadDemo.java:10)
at com.center.JavaVirtualThreadDemo$$Lambda/0x0000000027042a10.run(Unknown Source)
at java.lang.Thread.runWith(java.base@25/Thread.java:1487)
at java.lang.VirtualThread.run(java.base@25/VirtualThread.java:456)
at java.lang.VirtualThread$VThreadContinuation$1.run(java.base@25/VirtualThread.java:248)
at jdk.internal.vm.Continuation.enter0(java.base@25/Continuation.java:325)
at jdk.internal.vm.Continuation.enter(java.base@25/Continuation.java:316)
pinned threads
使用jcmd中的JFR或jfr命令查看被pin住的线程。
PS \openjdk-25> jcmd 7316 JFR.view verbose=true pinned-threads
7316:
Pinned Virtual Threads
Method Pinned Count Longest Pinning Total Time Pinned
(stackTrace.topApplicationFrame) (startTime) (duration) (duration)
--------------------------------------- ------------ --------------- -----------------
com.center.BlockingLoadClass.<clinit>() 1 10.4 s 10.4 s
COLUMN 'Method', 'Pinned Count', 'Longest Pinning', 'Total Time Pinned' SELECT
stackTrace.topApplicationFrame AS S, COUNT(*), MAXIMUM(duration), SUM(duration) AS T
FROM VirtualThreadPinned GROUP BY S ORDER BY T DESC
Execution: query-validation=0 s, aggregation=58.0 ms, formatting=10.0 ms
Timespan: 17:49:06 - 17:59:06
官方文档
———         Thanks for Reading         ———
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓