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

Reply via email to