Virtual threads created more platform threads

Jianbin Chen jianbin at apache.org
Wed Jul 2 04:51:56 UTC 2025


Hi everyone,
thank you for your replies and assistance. So this issue occurring in JDK
21 is considered normal behavior, correct? However, I've observed that
these expanded threads are not being garbage collected - they remain in a
waiting state for several minutes without disappearing. Sometimes they can
expand to several hundred threads, and these threads, once created, remain
unused, continuously wasting thread resources. Therefore, I'm attaching a
local reproduction example of this issue to ensure the information in this
email is complete.
-Djava.util.concurrent.ForkJoinPool.common.parallelism=1
`
The following example clearly shows the forkjoinpool expanding. I have also
recorded the runtime stack trace for this and submitted it as an attachment.

```
    public static void main(String[] args) throws InterruptedException {
        Executor executor =
ThreadPoolFactory.newVirtualThreadPerTaskExecutor();
        String a = "a";
        executor.execute(() -> {
            synchronized (a) {
            try {
            a.wait();
            } catch (InterruptedException e) {
            throw new RuntimeException(e);
            }
            }
        });
        executor.execute(() -> {
            synchronized (a) {
            try {
            a.wait();
            } catch (InterruptedException e) {
            throw new RuntimeException(e);
            }
            }
        });
        Thread.sleep(120000);
    }
```
After switching to using condition, it can be clearly observed through
jstack that the forkjoinpool did not expand as many threads, but 3 threads
still appeared, which might be related to incorrect usage of my JVM
parameters.

```
 public static void main(String[] args) throws InterruptedException {
        Executor executor =
ThreadPoolFactory.newVirtualThreadPerTaskExecutor();
        List<ReentrantLock> list = new ArrayList<>();
        list.add(new ReentrantLock());
        list.add(new ReentrantLock());
        list.add(new ReentrantLock());
        list.add(new ReentrantLock());
        list.add(new ReentrantLock());
        list.add(new ReentrantLock());
        for (int i = 0; i < list.size(); i++) {
            ReentrantLock value = list.get(i);
       Condition condition = value.newCondition();
            executor.execute(() -> {
                value.lock();
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }finally {
                        value.unlock();
                    }
            });
        }
        Thread.sleep(120000);
    }
```

Best Regards.
Jianbin Chen, github-id: funky-eyes

Peter Eastham <petereastham at gmail.com> 于 2025年7月2日周三 12:16写道:

> I hope to not create noise with my own comments, and I will concur with
> you that JEP 491 should mean this is resolved in Java 24, which Jianbin
> Chen should try out before and then alongside Robert's recommendation for
> creating a very simple reproduction.
>
> As Java 21 is still the current LTS, it isn't completely unreasonable to
> forward concerns to the mailing list. My understanding in this particular
> case is that JEP 491 is not going to be back ported to Java 21 as it has a
> dependency from a change in Java 23. (Potentially more, I can't remember
> the conversation completely, I only skimmed that email chain)
>
>
> Thanks,
> - Peter
>
> P.S. As I do a similar job, I'd like to call out that Vendors letting the
> occasional support question slip into here is a fine price to pay for the
> amount of questions they handle instead.
>
>
>
> On Tue, Jul 1, 2025, 9:17 PM Chen Liang <liangchenblue at gmail.com> wrote:
>
>> Hello, I don't think this would happen for JDK 24 - JEP 491 removed the
>> code that calls Blocker in Object.wait, which is exactly the goal of that
>> JEP.
>>
>> Note that virtual threads are still pinned when call stack goes into
>> native, as native execution may pass address to stack variables that will
>> be lost in context switches. In these cases, the traditional managed block
>> happens again.
>>
>> P.S. I personally think it is somewhat not responsible for JDK vendors to
>> ask users to upstream a question for an older JDK release that might no
>> longer apply on the latest release to a development-oriented mailing list.
>>
>> On Tue, Jul 1, 2025 at 9:47 PM Jianbin Chen <jianbin at apache.org> wrote:
>>
>>>
>>> Hi Loom-dev Community,
>>>
>>> I have a question about platform thread creation triggered by calling
>>> future.get() within virtual threads, and I would like to ask the community
>>> for assistance. The detailed information can be found in this issue:
>>> https://github.com/adoptium/adoptium-support/issues/1319. I hope to
>>> receive some help from the community regarding this matter. Thank you.
>>> Additionally, I'd like to know if this situation will still occur in JDK
>>> 24 and above?
>>>
>>> Best Regards.
>>> Jianbin Chen, github-id: funky-eyes
>>>
>>>
>>> Best Regards.
>>> Jianbin Chen, github-id: funky-eyes
>>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250702/402057ef/attachment-0001.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: jstack-after.log
Type: application/octet-stream
Size: 16371 bytes
Desc: not available
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250702/402057ef/jstack-after-0001.log>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: jstack.log
Type: application/octet-stream
Size: 10446 bytes
Desc: not available
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250702/402057ef/jstack-0001.log>


More information about the loom-dev mailing list