JDK-8282459 : Debug agent CLASS_UNLOAD event handling should attempt to deliver events on dedicated debug agent thread
  • Type: Bug
  • Component: core-svc
  • Sub-Component: debugger
  • Affected Version: 19
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • Submitted: 2022-02-28
  • Updated: 2022-07-22
  • Resolved: 2022-07-22
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 20
20Resolved
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
The current mechanism for generating JDWP CLASS_UNLOAD events is somewhat twisted, and leads to a couple of bugs. JVMTI has no (public) ClassUnload event support. It does for some reason in the extension mechanism, but it is not used. In order to determine when a class unloads, the debug agent tags (as in JVMTI SetTag) all Class instances. This results in the ObjectFree() callback being called each time a Class instance is collected. 

The ObjectFree() callbacks are not done by the GC. After GC is done (and after the GarbageCollectionFinish event has been posted), a Service Thread task collects all the tagged objects that have been freed. It then calls the following: 

// PostObjectFree can't be called by JavaThread, so call it from the VM thread. 
void JvmtiTagMap::post_dead_objects_on_vm_thread() { 
  VM_JvmtiPostObjectFree op(this); 
  VMThread::execute(&op); 
} 

So the posting of the ObjectFree events is deferred to the VM Thread, which unlike the Service Thread is not a Java Thread. Because it is not a JavaThread, JDWP events likely cannot be sent from it (so no immediately sending of the CLASS_UNLOAD event). It’s not clear why they can’t be sent, and possibly they can. More on below when discussing solutions.

Since the CLASS_UNLOAD events cannot be delivered from the VM Thread, the classes are instead queued up in a list called deletedSignatures. When JVMTI sends the GarbageCollectionFinish event (also from the VM Thread), the debug agent bumps up the garbageCollected counter. This counter is checked in event_callback(), which is called for any event received on a Java thread. At that point the deletedSignatures list is processed, and a CLASS_UNLOAD event is sent for each (and garbageCollected is cleared).

There are two bugs that arise from this deferred handling of CLASS_UNLOAD events:

    JDK-8256811 Delayed jdwp class unloading events
    JDK-8257705 vmTestbase/nsk/jdi/HiddenClass/events/events001.java timed out 

JDK-8256811 is due to the fact that the GarbageCollectionFinish event comes in before any ObjectFree events. This means that while ObjectFree events are being generated (and before all of them have been generated), it’s possible for some other JVMTI event to come in and trigger the garbageCollected check in event_callback(), which will result in processing the deletedSignatures list prematurely. After this is done more ObjectFree events come in and are added to the list, but they will not get processed until there is another GC, which might not be for a while.

JDK-8257705 is also related to the garbageCollected check. Simply put, once there is a GarbageCollectionFinish event, deletedSignatures is not processed until an event comes in on a Java thread. This potentially may not happen for a while, and theoretically may never happen. It depends of what types of EventRequests the debugger has currently setup, and what the application does to trigger them.  The test is doing nothing to ensure there is another event after it triggers the class unloading. It seems usually one does come in sporadically. It’s hard to say for sure what that event is, but is could be a CLASS_PREPARE triggered by some VM Java thread. In any case, the event is not reliably generated, so the test sometimes fails waiting for CLASS_UNLOAD event(s).

[NOTE: The solutions suggested below are no longer being suggested for the reason given in the second comment below. Instead the suggestion in the first comment seems to be a reasonable choice to fix these issues.]

As for a solution, what would fix this is being able to generate the CLASS_UNLOAD event immediately from the thread that the ObjectFree event was delivered on. So this currently would mean the VM Thread, but also potentially JVMTI could be changed to make it the Service Thread. Each potential has a problem.

For the VM Thread, the concern is that traditionally the debug agent has avoided sending CLASS_UNLOAD from the VM Thread. The reason is not clear. CLASS_UNLOAD is the only event that has this issue of the debug agent wanting to deliver it from the VM Thread. Unlike most other events, the event does not include the event thread, which is a good thing because we wouldn’t want the debugger being told the event occurred on the VM Thread. However, the EventRequest can request that the event thread be suspended, or all threads be suspended. If event thread suspension was chosen, the debugger can’t possibly know which thread that is, so it will have no choice but to use VM.resume() to resume it, which is the same thing it would do if all threads were suspended. Since the debugger can’t possibly know which thread the event occurred on, the debug agent may be able to get away with suspending the VM Thread as long as it gets resumed by VM.resume().

For the Service Thread case, it would be nice if rather than deferring to the VM Thread, the CLASS_UNLOAD events were just immediately delivered on the Service Thread. This presumably simplifies the above solution in that the event thread will be suspendable. There are a few concerns thought.

(1) JDK-8212879 moved the collecting of all tagged objects that were freed to the Service Thread, but Coleen said when she first did this, there was an issue posting the ObjectFree event from the Service Thread, so she kept the collecting on the Service Thread, but deferred the processing to the VM Thread by introducing the above mentioned JvmtiTagMap::post_dead_objects_on_vm_thread() method. It’s unclear what this issue is with posting directly from the Service Thread, but seemed to have been related to needing to do a vm state transition. Coleen suggested trying again and seeing.

(2) Suspending the Service Thread might not be such a good idea. In that case it would have to remain unsuspected much like the VM Thread in the first solution, and therefore subject to the same concerns expressed there.

(3) Although it is a Java Thread, the Service Thread is not a thread that the debug agent tracks because it is not returned by GetAllThreads. This makes it a challenge to suspend during event processing like other threads normally are. So suspended or not, we are back to having to special case it in various parts of the debug agent.

I’m inclined to suggest first trying the VM Thread solution, and not suspending it. We know the posting of ObjectFree on the VM Thread works (that’s how it is done now), so the question is can we get the debug agent to also generate the CLASS_UNLOAD event, and will there be any issues with not suspending the “event thread”.
Comments
The issues described in this CR have been fixed by JDK-8256811, so there is no longer a need to implement the suggested changes.
22-07-2022

There is one major advantage to processing the CLASS_UNLOAD events in bulk rather than sending each time there is an ObjectFree event. The debug agent takes advantage of the ability to group multiple CLASS_UNLOAD events into one composite command. So all of the CLASS_UNLOADs are sent in one command rather than a command per CLASS_UNLOAD. For this reason I think it would be best to use the suggestion in the above comment rather than those presented in the description, which involve sending the CLASS_UNLOAD each time there is an ObjectFree event.
07-03-2022

Another solution is to keep the current deferred approach, but find a way to reliably trigger the code in event_handler() that processes the deletedSignatures list. I think what is needed is a Java thread created by the debug agent and visible to the debugger so it can be suspended. Let's call it the ClassUnloadEventThread. The thread could wait on a JVMTI raw monitor. Whenever there is an ObjectFree event that results in adding the first item is to deletedSignatures, the monitor is notified. More items may be added to deletedSignatures before the ClassUnloadEventThread responds to being notified and wakes up. At that point it starts processing the list until empty (more items might get added while it is processing the list). Since this thread will only be able to send one event at a time, and sending an event requires queuing it up on yet another thread (and maybe even getting suspended itself and waiting for the debugger to resume), it's likely that by the time it is done processing the list, all ObjectFree events have been posted, so we won't have to deal with a bunch of notifications being required to deal with all the ObjectFree events that come in after a GC is necessary. It looks like the debug agent already creates similar java threads. See spawnNewThread(), which is used to create the various debug agent threads like the "JDWP Event Helper Thread". Basically it uses JNI to create a new Thread instance, and then calls JVMTI RunAgentThread, passing in the pointer the Thread instance and a pointer to a native function. So this might be pretty simple and spawnNewThread() can probably be reused for this purpose. We just need to have it conditionally do the following part, because we don't want the ClassUnloadEventThread thread to be a hidden from the debugger: error = threadControl_addDebugThread(thread); This should be pretty easy to implement since pretty much all of the needed code is already in place. It just needs to be executed from this new thread after waiting for the notification.
01-03-2022