Hi Patricio In our scenario, we encountered a similar issue that appears to stem from the same root cause. I’m happy to share the case details.
The problem arises in our custom class loader: when we use Logback to log messages, Logback attempts to acquire a lock during write operations. Meanwhile, a virtual thread that’s in the process of class initialization gets pinned to its carrier thread. As a result, other virtual threads attempting to use the same class end up being blocked with the message: "Waited for initialization of <class> by other thread". These waiting threads also get pinned to their carrier threads — but crucially, they do not enter <clinit>. This creates a deadlock-like situation where: One ForkJoinPool (FJP) worker thread is trying to acquire Logback’s lock. Other FJP workers are waiting on a non-existent ObjectMonitor (likely due to the pinned thread holding the monitor and not progressing). Interestingly, the stack traces appear to be in normal Java code, which makes the root cause non-obvious at first glance. "ForkJoinPool-1-worker-28" #799 [828] daemon prio=5 os_prio=0 cpu=47855486.95ms elapsed=258235.59s allocated=42813G defined_classes=287 tid=0x00007f4803ad9000 nid=828 waiting on condition [0x00007fd88ee65000] java.lang.Thread.State: WAITING (parking) Carrying virtual thread #101393 at jdk.internal.vm.Continuation.run([email protected]/Continuation.java:252) - parking to wait for <0x00007f4892f23810> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.lang.VirtualThread.runContinuation([email protected]/VirtualThread.java:299) at java.lang.VirtualThread$$Lambda/0x00007f484085cc48.run([email protected]/Unknown Source) at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec([email protected]/ForkJoinTask.java:1403) at java.util.concurrent.ForkJoinTask.doExec([email protected]/ForkJoinTask.java:387) at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec([email protected]/ForkJoinPool.java:1313) at java.util.concurrent.ForkJoinPool.scan([email protected]/ForkJoinPool.java:1844) at java.util.concurrent.ForkJoinPool.runWorker([email protected]/ForkJoinPool.java:1809) at java.util.concurrent.ForkJoinWorkerThread.run([email protected]/ForkJoinWorkerThread.java:188) "engine-44-111" #101393 Mounted virtual thread on "ForkJoinPool-1-worker-28" #799 at jdk.internal.misc.Unsafe.park([email protected]/Native Method) - parking to wait for <0x00007fd984009f38> (a java.util.concurrent.locks.ReentrantLock$NonfairSync) at java.lang.VirtualThread.parkOnCarrierThread([email protected]/VirtualThread.java:817) at java.lang.VirtualThread.park([email protected]/VirtualThread.java:755) at java.lang.System$2.parkVirtualThread([email protected]/System.java:2714) at java.util.concurrent.locks.LockSupport.park([email protected]/LockSupport.java:221) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire([email protected]/AbstractQueuedSynchronizer.java:754) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire([email protected]/AbstractQueuedSynchronizer.java:990) at java.util.concurrent.locks.ReentrantLock$Sync.lock([email protected]/ReentrantLock.java:153) at java.util.concurrent.locks.ReentrantLock.lock([email protected]/ReentrantLock.java:322) at ch.qos.logback.core.OutputStreamAppender.writeBytes(OutputStreamAppender.java:197) at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:231) at ch.qos.logback.core.rolling.RollingFileAppender.subAppend(RollingFileAppender.java:235) at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102) at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84) at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51) at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270) at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257) at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:421) at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383) at ch.qos.logback.classic.Logger.info(Logger.java:591) at com.example.loader.PluginClassLoader.reverseLoadClassInternal(PluginClassLoader.java:217) at com.example.loader.PluginClassLoader.loadClass(PluginClassLoader.java:132) - locked <0x00007fd9b83dabc0> (a com.example.loader.SharedPluginClassLoader) at java.lang.ClassLoader.loadClass([email protected]/ClassLoader.java:526) at com.example.MyExample.init(MyExample.java:34) at com.example.TaskHelper$$Lambda/0x00007f484149dec0.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker([email protected]/ThreadPoolExecutor.java:1144) at java.util.concurrent.ThreadPoolExecutor$Worker.run([email protected]/ThreadPoolExecutor.java:642) at java.lang.Thread.runWith([email protected]/Thread.java:1607) at java.lang.VirtualThread.run([email protected]/VirtualThread.java:462) at java.lang.VirtualThread$VThreadContinuation$1.run([email protected]/VirtualThread.java:254) at jdk.internal.vm.Continuation.enter0([email protected]/Continuation.java:326) at jdk.internal.vm.Continuation.enter([email protected]/Continuation.java:317) "ForkJoinPool-1-worker-23" #786 [828] daemon prio=5 os_prio=0 cpu=55.73ms elapsed=51857.88s allocated=1659K defined_classes=6 tid=0x00007fd92f9de800 nid=828 in Object.wait() [0x00007fd88ef67000] java.lang.Thread.State: RUNNABLE Carrying virtual thread #1265 at jdk.internal.vm.Continuation.run([email protected]/Continuation.java:255) at java.lang.VirtualThread.runContinuation([email protected]/VirtualThread.java:299) at java.lang.VirtualThread$$Lambda/0x00007fd93b7e4068.run([email protected]/Unknown Source) at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec([email protected]/ForkJoinTask.java:1423) at java.util.concurrent.ForkJoinTask.doExec([email protected]/ForkJoinTask.java:387) at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec([email protected]/ForkJoinPool.java:1313) at java.util.concurrent.ForkJoinPool.scan([email protected]/ForkJoinPool.java:1844) at java.util.concurrent.ForkJoinPool.runWorker([email protected]/ForkJoinPool.java:1809) at java.util.concurrent.ForkJoinWorkerThread.run([email protected]/ForkJoinWorkerThread.java:188) "arkhouyi-houyi-pool-3-5" #1265 Mounted virtual thread on "ForkJoinPool-1-worker-23" #786 at com.example.ExampleAccessor.getExample(ExampleAccessor.java:57) at com.example.TaskHelper$$Lambda/0x00007fd93c47e4d8.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker([email protected]/ThreadPoolExecutor.java:1144) at java.util.concurrent.ThreadPoolExecutor$Worker.run([email protected]/ThreadPoolExecutor.java:642) at java.lang.Thread.runWith([email protected]/Thread.java:1607) at java.lang.VirtualThread.run([email protected]/VirtualThread.java:462) at java.lang.VirtualThread$VThreadContinuation$1.run([email protected]/VirtualThread.java:254) at jdk.internal.vm.Continuation.enter0([email protected]/Continuation.java:326) at jdk.internal.vm.Continuation.enter([email protected]/Continuation.java:317) Xialin Liu Original From: loom-dev-request <[email protected]> Date: 2025-11-04 02:08 To: loom-dev <[email protected]> Subject: loom-dev Digest, Vol 95, Issue 2 Hi Danny, I see a couple of FJP workers that don?t seem to be carrying a virtual thread blocked on clinit. Do you have the full stacktraces? If available, the native stack traces would also be useful to verify the entry point into the VM for the ones blocked at clinit. Thanks, Patricio
