[External] : Re: Virtual thread memory leak because TrackingRootContainer keeps threads

Robert Engels robaho at icloud.com
Thu Jul 25 15:17:20 UTC 2024


I have created a very simple unbounded FIFO queue designed for use with virtual threads to better ensure thread clean-up without the reliance of automatic clean-up of VTs.

You can review the project here https://github.com/robaho/closablequeue

> On Jul 25, 2024, at 9:44 AM, Matthew Swift <matthew.swift at gmail.com> wrote:
> 
> I agree Robert, that's my understanding from Alan's previous response:
> 
> * when blocked, a VT is referenced by the scheduler or wait lists on locks, etc
> * when running, a VT is referenced by the carrier thread.
> 
> From a life-cycle point of view, a VT "is a" Thread, as you correctly say.
> 
> 
> On Thu, 25 Jul 2024 at 15:52, Robert Engels <robaho at icloud.com <mailto:robaho at icloud.com>> wrote:
> If it is blocked reading a socket, it is not on a CarrierThread (i.e. blocked not running) so there is no strong reference from the CarrierThread to the VirtualThread in this case.
> 
> I am fairly certain that the scheduler/blocking handler is what maintains the strong thread references in this case.
> 
>> On Jul 25, 2024, at 8:49 AM, Matthew Swift <matthew.swift at gmail.com <mailto:matthew.swift at gmail.com>> wrote:
>> 
>> Thanks for the quick response Alan. A couple of comments:
>> 
>> > You can specify a ThreadFactory to newThreadPerTaskExecutor so you can
>> > set the thread name if you need it. Virtual threads are meant to be
>> > lightweight and numerous so don't get a name by default. For many cases,
>> > this is okay as the they have a thread ID which will identity them in
>> > thread dumps and elsewhere.
>> 
>> That may be so, but it's not very flexible, unless I'm missing something, because it's not possible to derive the thread name from the task itself. Said otherwise, a ThreadFactory lets you create very generic thread names like "HTTP Connection #1" ahead of time, but doesn't let you derive a rich name from the Runnable itself, like "HTTP Connection from 1.2.3.4 to 5.6.7.8 on port 8080". I tried cheating a bit by relying on the toString() of the Runnable, but sadly this doesn't work because the executor wraps the provided Runnable before calling the thread factory (see ThreadPerTaskExecutor#start(java.lang.Runnable)).
>> 
>> > A virtual thread that is blocked reading from a Socket will continue
>> > when there are bytes to read, the peer closes the connection, some I/O
>> > exception, or the thread is interrupted. So it will be strongly reachable.
>> 
>> Ah, of course, silly me. And, following from that, the VT is strongly reachable when running because it is directly referenced by the carrier thread itself.
>> 
>> Thanks
>> Matt
>> 
>> 
>> On Thu, 25 Jul 2024 at 14:55, Alan Bateman <Alan.Bateman at oracle.com <mailto:Alan.Bateman at oracle.com>> wrote:
>> 
>> 
>> On 25/07/2024 13:21, Matthew Swift wrote:
>> > Hmm, I'm starting to think I may have fallen into the same trap here 
>> > as Michal.
>> >
>> > I've been using virtual threads similar to platform threads for 
>> > performing IO tasks asynchronously.
>> >
>> > Background: originally, I was using 
>> > Executors#newVirtualThreadPerTaskExecutor to run these tasks, which 
>> > was fine because the Executor tracks the virtual threads it creates in 
>> > order to support shutdown. However, from an observability point of 
>> > view I've found the executor to be a bit frustrating because it is 
>> > impossible to set the thread name before the thread is scheduled to 
>> > run[1].
>> You can specify a ThreadFactory to newThreadPerTaskExecutor so you can 
>> set the thread name if you need it. Virtual threads are meant to be 
>> lightweight and numerous so don't get a name by default. For many cases, 
>> this is okay as the they have a thread ID which will identity them in 
>> thread dumps and elsewhere.
>> 
>> 
>> > This means that under heavy load where the FJ pool is busy a thread 
>> > dump shows many unnamed threads, which are waiting to be scheduled for 
>> > the first time. Admittedly, this is only a minor annoyance because I'm 
>> > more interested in the threads that are clogging up the FJ pool than 
>> > the ones which are waiting to use it, even so it'd be nice to have an 
>> > overall picture of what's active and what's queued (note also thread 
>> > names are included in JFR events, which is super helpful).
>> 
>> With the Loom EA builds [1] you can use `jcmd <pid> 
>> Thread.vthread_summary` to get a summary view of all thread grouping so 
>> you get thread counts, a summary of the scheduler, timers, and any 
>> socket I/O that it outstanding. We would like to bring this diagnostic 
>> command into the main line JDK at some point.
>> 
>> >
>> > To remedy this, I've switched away from using an Executor and now I 
>> > just use "Thread.ofVirtual().name(initialName).start(task)". However, 
>> > I don't think all of the tasks are strongly reachable - some are "fire 
>> > and forget" tasks (e.g. async resource cleanup), so I may be 
>> > inadvertently relying on the JVM's observability support to keep these 
>> > tasks alive until they complete, which seems a bit brittle. In fact, 
>> > now that I think of it, it may not even be limited to fire and forget 
>> > tasks. A VT that is reading messages from a Socket could be GC'd
>> A virtual thread that is blocked reading from a Socket will continue 
>> when there are bytes to read, the peer closes the connection, some I/O 
>> exception, or the thread is interrupted. So it will be strongly reachable.
>> 
>> -Alan
>> 
>> [1] https://jdk.java.net/loom/ <https://jdk.java.net/loom/>
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20240725/59680c5d/attachment.htm>


More information about the loom-dev mailing list