<div dir="auto"><div dir="auto">Hi everyone,</div><div dir="auto"><br></div><div dir="auto">After upgrading to JDK 25 and switching to virtual threads, I've run into a performance issue that I think is worth discussing.</div><div dir="auto"><br></div><div dir="auto">When using the non-pooled style:</div><div dir="auto"><br></div><div dir="auto">```java</div><div dir="auto">Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("vt-", 1).factory());</div><div dir="auto">```</div><div dir="auto"><br></div><div dir="auto">I observed a **significant performance degradation** in workloads that rely on ThreadLocal-based buffer pools in third-party libraries. A concrete example is the Aerospike Java client:</div><div dir="auto"><br></div><div dir="auto"><a href="https://github.com/aerospike/aerospike-client-java/blob/master/client/src/com/aerospike/client/util/ThreadLocalData.java">https://github.com/aerospike/aerospike-client-java/blob/master/client/src/com/aerospike/client/util/ThreadLocalData.java</a></div><div dir="auto"><br></div><div dir="auto">Because each virtual thread gets its own ThreadLocal instance, and virtual threads are created per task (no pooling), these libraries end up allocating fresh multi-KB byte buffers repeatedly in each new virtual thread. This causes:</div><div dir="auto"><br></div><div dir="auto">- A massive surge in short-lived object allocations  </div><div dir="auto">- Dramatically increased GC pressure  </div><div dir="auto">- Noticeably higher CPU usage  </div><div dir="auto"><br></div><div dir="auto">Even though virtual threads are cheap, the GC overhead ends up eating most of the benefits we expect from virtual threads.</div><div dir="auto"><br></div><div dir="auto">However, when I switched to a **pooled virtual thread executor** like this:</div><div dir="auto"><br></div><div dir="auto">```java</div><div dir="auto">new ThreadPoolExecutor(</div><div dir="auto">    200,                       // corePoolSize</div><div dir="auto">    Integer.MAX_VALUE,</div><div dir="auto">    60, TimeUnit.SECONDS,</div><div dir="auto">    new SynchronousQueue<>(),</div><div dir="auto">    Thread.ofVirtual().name("vt-pool-", 1).factory()</div><div dir="auto">);</div><div dir="auto">```</div><div dir="auto"><br></div><div dir="auto">The performance improved **dramatically**:</div><div dir="auto"><br></div><div dir="auto">- At least 200 virtual threads are reused → most ThreadLocal caches are hit and reused  </div><div dir="auto">- GC pressure drops significantly  </div><div dir="auto">- CPU usage decreases accordingly  </div><div dir="auto"><br></div><div dir="auto">I ran benchmarks comparing:</div><div dir="auto"><br></div><div dir="auto">1. Pure newThreadPerTaskExecutor (unpooled virtual threads)  </div><div dir="auto">2. The above ThreadPoolExecutor style (pooled virtual threads)  </div><div dir="auto"><br></div><div dir="auto">Even when I set `keepAliveTime = 0L` (so only the 200 core threads are kept alive long-term and extras are immediately reclaimed), **the pooled version still clearly outperformed the unpooled one**.</div><div dir="auto"><br></div><div dir="auto">So my question is:</div><div dir="auto"><br></div><div dir="auto">**In scenarios where third-party libraries heavily rely on ThreadLocal for caching / buffering (and we cannot change those libraries to use object pools instead), is explicitly pooling virtual threads (using a ThreadPoolExecutor with virtual thread factory) considered a recommended / acceptable workaround?**</div><div dir="auto"><br></div><div dir="auto">Or are there better / more idiomatic ways to handle this kind of compatibility issue with legacy ThreadLocal-based libraries when migrating to virtual threads?</div><div dir="auto"><br></div><div dir="auto">I have already opened a related discussion in the Dubbo project (since Dubbo is one of the libraries affected in our stack):</div><div dir="auto"><br></div><div dir="auto"><a href="https://github.com/apache/dubbo/issues/16042">https://github.com/apache/dubbo/issues/16042</a></div><div dir="auto"><br></div><div dir="auto">Would love to hear your thoughts — especially from people who have experience running large-scale virtual-thread-based services with mixed third-party dependencies.</div><div dir="auto"><br></div><div dir="auto">Thanks in advance!</div><div dir="auto"><br></div><div dir="auto"><br><div data-smartmail="gmail_signature" dir="auto">Best Regards.<br>Jianbin Chen, github-id: funky-eyes </div></div></div>