On Tue, 25 Nov 2025 02:25:02 GMT, Chris Plummer <[email protected]> wrote:
> Don't enabled VIRTUAL_THREAD_START/END events unless absolutely necessary.
> Solves performance issues when trying to debug apps that create a lot of
> virtual threads. Details in first comment.
>
> With these changes the Skynet benchmark no longer shows any slowdown when
> launching with debugging enabled or when attaching the debugger.
>
> Tested with all tier2, tier3, tier5, and tier6 CI testing (with filters to
> only run svc tests).
The debug agent always enables VIRTUAL_THREAD_START/END. This is very
detrimental to performance with apps that create a lot of virtual therads. The
flood of events bogs down the debug agent, and also slows down JVMTI which
needs to create a jvmtiThreadState for each thread just to deliver the event to
the debug agent.
If the debug agent includevirtualthreads option is not set to 'y' (it is 'n' by
default), then it is possible for the debug agent to start up without enabling
VIRTUAL_THREAD_START/END events (currently it always enables them). However,
they need to be enabled if the debugger creates any ThreadStartRequests or
ThreadDeathRequests and do not include the PlatformThreadsOnly filter. Given
that by default jdb and mainstream debuggers all seem to add this filter, this
is unlikely to be an issue.
The changes in this PR make it so the debug agent no longer automatically
enables all VIRTUAL_THREAD_START/END (unless VIRTUAL_THREAD_START=y), and
instead only enables them based on to the presence of ThreadStartRequests or
ThreadDeathRequests that don't set the PlatformThreadsOnly filter.
Also, for ThreadDeathRequests that don't set the PlatformThreadsOnly filter but
do set the thread filter, VIRTUAL_THREAD_END events are only enabled for the
thread that is being filtered on. This is important for debuggers (like jdb)
that register a ThreadDeathRequest for any ThreadReference the debugger is
tracking, and want to stop tracking once the thread exits.
One challenge with these changes was getting the event enabling/disabling right
in eventFilter.c. Disabling was especially tricky because when removing an
event request, all other event requests (of the same kind) have to be examined
to see if any still require the event to be enabled. If there is a filter
thread, that also needs to be considered.
The other challenge was dealing with the fact that without VIRTUAL_THREAD_END
events, you can end up with terminated threads on the runningVThreads list.
There were a few places that had to be special cased to deal with this.
src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c line 1640:
> 1638:
> 1639: if (gdata->vthreadsSupported) {
> 1640: // Now is a good time to garbage collect vthread nodes.
The original comment at one point was true while I was working on the previous
PR that introduced it. However, I later added a check for (node->suspendCount
== suspendAllCount) to freeUnusedVThreadNode(). If true, then we can free the
ThreadNode even though it has a non-zero suspendCount. If the ThreadNode is
recreated at some point in the future, it will get suspendAllCount assigned to
node->suspendCount.
-------------
PR Comment: https://git.openjdk.org/jdk/pull/28485#issuecomment-3573531199
PR Review Comment: https://git.openjdk.org/jdk/pull/28485#discussion_r2558355260