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

Michal Domagala outsider404 at gmail.com
Thu Jul 11 11:38:15 UTC 2024


> In some cases then having the producer offering a special element to mean
"done" can work as a signal to the consumer to shutdown.

If the producer decides about shutdown, poison pill signal is a well known
solution: simple and synchronization-less. If the other party decides to
shut down, the poison pill does not work. Shutdown needs synchronization -
or ephemeral VT shutdowned by GC.

Regardless of the main issue, I understand that VT GC is problematic. JEP
444 is modified and GC paragraph is removed.
VT created by Thread API is strongly referenced to avoid finalization
problems
VT created by Executors.newVirtualThreadPerTaskExecutor()
or Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory()) are not
strongly referenced due oscillating nature of Executor. Moreover, Executor
and submit product Future is AutoCloseable. Being autocloseable +
try-with-resource means it is easy to avoid finalization problems in code.

Before try-with-resources, it was well known that some resources, like file
descriptors, should not be released by GC. It was the developer's
responsibility to close unused file descriptors. Try-with-resources
simplifies safe implementation, but the rule is still valid. If releasing
something by GC is problematic, it is the developer responsibility to
provide safe implementation.

In my opinion, wrong decisions are made.

Having all VT ephemeral simplifies the system and allows a simpler
programming model. If releasing resources by GC is problematic, it should
be a developer problem, not JVM problem.

If some VT are strongly referenced and some not, and this behavior is
undocumented, whole system is unpredictable for developers, who are not
subscribers of this group

Thanks everybody for collaboration

PS. I JEP 444 I still see GC root paragraph:

Unlike platform thread stacks, virtual thread stacks are not GC roots. Thus
the references they contain are not traversed in a stop-the-world pause by
garbage collectors, such as G1, that perform concurrent heap scanning.

What does it mean? I do not understand this paragraph. Will G1 clean VT or
not?

czw., 11 lip 2024 o 11:00 Alan Bateman <Alan.Bateman at oracle.com> napisał(a):

> On 10/07/2024 20:56, Michal Domagala wrote:
>
> Hi,
>
> My issue is accepted as Bug ID: JDK-8336061 Virtual threads cannot be
> GC'ed before they terminate (java.com)
> <https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8336061> and
> solved as documentation defect.
>
>
> You can view the issue in JBS here:
> https://bugs.openjdk.org/browse/JDK-8336061
>
> As noted, we missed an update to a paragraph when copying text from JEP
> 425/436 to JEP 444 so it wasn't aligned with the change summarized in the
> History section of JEP 444. That has been fixed. Otherwise, we can only
> close the JBS issue for now as support for some kind of ephemeral threads
> is definitely future exploration.
>
>
> Still one issue is not clear to me. Is VT strongly referenced because
> being blocked and GC'ed is not useful or is problematic to implement?
>
> There were voices in discussion that VT GC  is surprising for developers.
> For that reason I showed my case. I wanted to present that "closing"
> blocking queue by nullifying and GC does not need synchronization.
> Unsynchronized code is less prone to errors than code with synchronization.
>
>
> In some cases then having the producer offering a special element to mean
> "done" can work as a signal to the consumer to shutdown. Some form of
> closable channel would be similar. I think this project has a good handle
> on possible use-cases so we know well that it's not as simple as this in
> some cases.
>
> In the Golang world the phrases used are "forgotten sender" and "abandoned
> receiver" when goroutines "leak". This seems to lead to complicated recipes
> for signalling and shutdown. Java has had threads since JDK 1.0 and it
> doesn't seem to arise as often, one reason could be that you can keep a
> reference to a Thread, another might be that the
> serviceability/observability support is good so it is possible to identify
> orphaned threads and their stack traces, maybe reason about why they did
> not terminate when expected.
>
>
> But there are voices that garbage collection "alive" VT is problematic:
> "Finalization is one topic. Another is Cleaner actions that may run while a
> resource is in an inconsistent state." I don't know this area so I can't
> deny it.
>
> I checked that the blocked task created by Executors.
> newVirtualThreadPerTaskExecutor().submit() is GCed.
>
>
> Considerable time was spent on this topic before making virtual threads a
> permanent feature in JDK 21. We decided to leave this as is for now. In the
> case of a Thread-Per-Task-Executor (TPTE) then it keeps a reference to all
> threads executing tasks. It has to because of shutdownNow or close that may
> have to interrupt all threads. A TPTE is typically stored somewhere, often
> a static final field, or it will be used with try-with-resources. So once a
> TPTE is strongly reachable then it will ensure that all virtual threads
> executing tasks in that executor are keep reachable. There is a similar
> story for other "unowned" executor service implementations. For now,
> unowned executor services are weakly tracked for observability purposes.
> There is an argument that they should be strongly tracked when there is at
> least one thread but that adds some bookkeeping overhead, esp. when
> oscillating between zero and a single thread. So everything you are probing
> here is possible future work.
>
> -Alan
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20240711/cd7cbd68/attachment.htm>


More information about the loom-dev mailing list