Hi Xialin,
On 11/3/25 10:17 PM, Xialin Liu wrote:
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).
Yes, this is the issue addressed in JDK-8369238 for the common
initialization paths. (Note that the initialization lock of a class is
implemented using an internal Java monitor, so this is probably the one
you are observing).
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)
I’m confused about this stacktrace because I don’t see the <clinit> on
the stack. If you could send the stacktraces of all threads, including
the native stacks, that would be helpful.
Thanks,
Patricio