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

Reply via email to