Hi Chris,

> I assume JVMTI maintains separate tagging data for each agent so having
> two agents doing tagging won't result in confusion. I didn't actually
> find this in the spec. Would be nice to confirm that it is the case.
> However, your implementation does seem to conflict with other uses of
> tagging in the debug agent:

The tagging data is per-jvmtiEnv. We create and use our own env (private
to class-tracking), so this wouldn't conflict with other uses of tags.
Could it be a problem that we have a single trackingEnv per JVM, though?
/me scratches head.

> What would cause classTrack_addPreparedClass() to be called for a Class
> you've already seen? I don't understand the need for the "tag != 0l" check.

It's probably not needed, may be a left-over from previous installments
of this implementation. I will check it, and turn into an assert or so.

Thanks,
Roman

> thanks,
> 
> Chris
> 
> On 3/20/20 12:52 PM, Chris Plummer wrote:
>> On 3/20/20 8:30 AM, Roman Kennke wrote:
>>> I believe I came up with a much simpler solution that also solves the
>>> problems of the existing one, and the ones I proposed earlier.
>>>
>>> It turns out that we can take advantage of the fact that we can use
>>> *anything* as tags in JVMTI, even pointers to stuff (this is explicitely
>>> mentioned in the JVMTI spec). This means we can simply stick a pointer
>>> to the signature of a class into the tag, and pull it out again when we
>>> get notified that the class gets unloaded.
>>>
>>> This means we don't need an extra data-structure to keep track of
>>> classes and signatures, and it also makes the story around locking
>>> *much* simpler. Performance-wise this is O(1), i.e. no scanning of all
>>> classes needed (as in the current implementation) and no searching of
>>> table needed (like in my previous attempts).
>>>
>>> Please review this new revision:
>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.06/
>> I'll have a look at this.
>>>
>>> (Notice that there still appears to be a performance bottleneck with
>>> class-unloading when an actual debugger is attached. This doesn't seem
>>> to be related to the classTrack.c implementation though, but looks like
>>> a consequence of getting all those class-unload notifications over the
>>> wire. My testcase generates 1000s of them, and it's clogging up the
>>> buffers.)
>> At least this is only a one-shot hit when the classes are unloaded,
>> and the performance hit is based on the number of classes being
>> unloaded. The main issue is happening every GC, and is O(n) where n is
>> the number of loaded classes.
>>> I am not sure why jdb needs to enable class-unload listener always. A
>>> simple hack disables it, and performance is brilliant, even when jdb is
>>> attached:
>>> http://cr.openjdk.java.net/~rkennke/disable-jdk-class-unload.patch
>> This is JDI, not jdb. It looks like it needs ClassUnload events so it
>> can maintain typesBySignature, which is used by public APIs like
>> allClasses(). So we have caching of loaded classes both in the debug
>> agent and in JDI.
>>
>> Chris
>>> But this is not in the scope of this bug.)
>>>
>>> Roman
>>>
>>>
>>> On 3/16/20 8:05 AM, serguei.spit...@oracle.com wrote:
>>>> Sorry, forgot to complete my comments at the end (see below).
>>>>
>>>>
>>>> On 3/15/20 23:57, serguei.spit...@oracle.com wrote:
>>>>> Hi Roman,
>>>>>
>>>>> Thank you for the update and sorry for the latency in review.
>>>>>
>>>>> Some comments are below.
>>>>>
>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.05/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c.frames.html
>>>>>
>>>>>
>>>>> 87 cbTrackingObjectFree(jvmtiEnv* jvmti_env, jlong tag)
>>>>>    88 {
>>>>> 89 debugMonitorEnter(deletedSignatureLock);
>>>>> 90 if (currentClassTag == -1) {
>>>>> 91 // Class tracking not initialized, nobody's interested
>>>>> 92 debugMonitorExit(deletedSignatureLock);
>>>>> 93 return;
>>>>>    94     }
>>>>> Just a question:
>>>>>    Q1: Should the ObjectFree events be disabled for the jvmtiEnv
>>>>> that does
>>>>>        the class tracking if class tracking has not been initialized?
>>>>>
>>>>> 70 static jlong currentClassTag; I'm thinking if the name is better to
>>>>> be something like: lastClassTag or highestClassTag.
>>>>>
>>>>> 99 KlassNode* klass = *klass_ptr;
>>>>> 100 102 while (klass != NULL && klass->klass_tag != tag) { 103
>>>>> klass_ptr = &klass->next; 104 klass = *klass_ptr;
>>>>> 105 } 106 if (klass != NULL || klass->klass_tag != tag) { // klass not
>>>>> found - ignore.
>>>>> 107 debugMonitorExit(deletedSignatureLock);
>>>>> 108 return;
>>>>>   109     }
>>>>>   It seems to me, something is wrong in the condition at L106 above.
>>>>>   Should it be? :
>>>>>      if (klass == NULL || klass->klass_tag != tag)
>>>>>
>>>>>   Otherwise, how can the second check ever work correctly as the
>>>>> return
>>>>> will always happen when (klass != NULL)?
>>>>>
>>>>>   There are several places in this file with the the indent:
>>>>> 90 if (currentClassTag == -1) {
>>>>> 91 // Class tracking not initialized, nobody's interested
>>>>> 92 debugMonitorExit(deletedSignatureLock);
>>>>> 93 return;
>>>>>    94     }
>>>>>   ...
>>>>> 152 if (currentClassTag == -1) {
>>>>> 153 // Class tracking not initialized yet, nobody's interested
>>>>> 154 debugMonitorExit(deletedSignatureLock);
>>>>> 155 return;
>>>>>   156     }
>>>>>   ...
>>>>> 161 if (error != JVMTI_ERROR_NONE) {
>>>>> 162 EXIT_ERROR(error, "Unable to GetTag with class trackingEnv");
>>>>>   163     }
>>>>> 164 if (tag != 0l) {
>>>>> 165 debugMonitorExit(deletedSignatureLock);
>>>>> 166 return; // Already added
>>>>>   167     }
>>>>>   ...
>>>>> 281 cleanDeleted(void *signatureVoid, void *arg)
>>>>> 282 {
>>>>> 283 char* sig = (char*)signatureVoid;
>>>>> 284 jvmtiDeallocate(sig);
>>>>> 285 return JNI_TRUE;
>>>>>   286 }
>>>>>   ...
>>>>>   291 void
>>>>>   292 classTrack_reset(void)
>>>>>   293 {
>>>>> 294 int idx;
>>>>> 295 debugMonitorEnter(deletedSignatureLock);
>>>>> 296
>>>>> 297 for (idx = 0; idx < CT_SLOT_COUNT; ++idx) {
>>>>> 298 KlassNode* node = table[idx];
>>>>> 299 while (node != NULL) {
>>>>> 300 KlassNode* next = node->next;
>>>>> 301 jvmtiDeallocate(node->signature);
>>>>> 302 jvmtiDeallocate(node);
>>>>> 303 node = next;
>>>>> 304 }
>>>>> 305 }
>>>>> 306 jvmtiDeallocate(table);
>>>>> 307
>>>>> 308 bagEnumerateOver(deletedSignatureBag, cleanDeleted, NULL);
>>>>> 309 bagDestroyBag(deletedSignatureBag);
>>>>> 310
>>>>> 311 currentClassTag = -1;
>>>>> 312
>>>>> 313 (void)JVMTI_FUNC_PTR(trackingEnv,DisposeEnvironment)(trackingEnv);
>>>>> 314 trackingEnv = NULL;
>>>>> 315
>>>>> 316 debugMonitorExit(deletedSignatureLock);
>>>>>
>>>>> Could you, please, fix several comments below?
>>>>> 63 * The JVMTI tracking env to keep track of klass tags, for
>>>>> class-unloads
>>>>>   The comma is not needed.
>>>>>   Would it better to replace: klass tags => klass_tag's ?
>>>>>
>>>>>
>>>>> 73 * Lock to keep table, currentClassTag and deletedSignatureBag
>>>>> consistent
>>>>>   Maybe: Lock to guard ... or lock to keep integrity of ...
>>>>>
>>>>> 84 * Callback when classes are freed, Finds the signature and
>>>>> remembers it in deletedSignatureBag. Would be better to use words like
>>>>> "store" or "record", "Find" should not start from capital letter:
>>>>> Invoke the callback when classes are freed, find and record the
>>>>> signature in deletedSignatureBag.
>>>>>
>>>>> 96 // Find deleted KlassNode 133 // Class tracking not initialized,
>>>>> nobody's interested 153 // Class tracking not initialized yet,
>>>>> nobody's interested 158 /* Check this is not a duplicate */ Missed dot
>>>>> at the end. 106 if (klass != NULL || klass->klass_tag != tag) { //
>>>>> klass not found - ignore. In opposite, dot is not needed as the
>>>>> comment does not start from a capital letter. 111 // At this point we
>>>>> have the KlassNode corresponding to the tag
>>>>> 112 // in klass, and the pointer to it in klass_node.
>>>>   The comment above can be better. Maybe, something like:
>>>>     " At this point, we found the KlassNode matching the klass
>>>> tag(and it is
>>>> linked).
>>>>
>>>>> 113 // Remember the unloaded signature.
>>>>   Better: Record the signature of the unloaded class and unlink it.
>>>>
>>>> Thanks,
>>>> Serguei
>>>>
>>>>> Thanks,
>>>>> Serguei
>>>>>
>>>>> On 3/9/20 05:39, Roman Kennke wrote:
>>>>>> Hello all,
>>>>>>
>>>>>> Can I please get reviews of this change? In the meantime, we've done
>>>>>> more testing and also field-/torture-testing by a customer who is
>>>>>> happy
>>>>>> now. :-)
>>>>>>
>>>>>> Thanks,
>>>>>> Roman
>>>>>>
>>>>>>
>>>>>>> Hi Serguei,
>>>>>>>
>>>>>>> Thanks for reviewing!
>>>>>>>
>>>>>>> I updated the patch to reflect your suggestions, very good!
>>>>>>> It also includes a fix to allow re-connecting an agent after
>>>>>>> disconnect,
>>>>>>> namely move setup of the trackingEnv and deletedSignatureBag to
>>>>>>> _activate() to ensure have those structures after re-connect.
>>>>>>>
>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.05/
>>>>>>>
>>>>>>> Let me know what you think!
>>>>>>> Roman
>>>>>>>
>>>>>>>> Hi Roman,
>>>>>>>>
>>>>>>>> Thank you for taking care about this scalability issue!
>>>>>>>>
>>>>>>>> I have a couple of quick comments.
>>>>>>>>
>>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.04/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c.frames.html
>>>>>>>>
>>>>>>>>
>>>>>>>> 72 /*
>>>>>>>> 73 * Lock to protect deletedSignatureBag
>>>>>>>> 74 */
>>>>>>>> 75 static jrawMonitorID deletedSignatureLock; 76 77 /*
>>>>>>>> 78 * A bag containing all the deleted classes' signatures. Must be
>>>>>>>> accessed under
>>>>>>>> 79 * deletedTagLock,
>>>>>>>>    80  */
>>>>>>>> 81 struct bag* deletedSignatureBag;
>>>>>>>>
>>>>>>>>    The comments contradict to each other.
>>>>>>>>    I guess, the lock name at line 79 has to be deletedSignatureLock
>>>>>>>> instead of deletedTagLock.
>>>>>>>>    Also, comma at the end must be replaced with dot.
>>>>>>>>
>>>>>>>>
>>>>>>>> 101 // Tag not found? Ignore.
>>>>>>>> 102 if (klass == NULL) {
>>>>>>>> 103 debugMonitorExit(deletedSignatureLock);
>>>>>>>> 104 return;
>>>>>>>> 105 }
>>>>>>>>   106
>>>>>>>> 107 // Scan linked-list.
>>>>>>>> 108 jlong found_tag = klass->klass_tag;
>>>>>>>> 109 while (klass != NULL && found_tag != tag) {
>>>>>>>> 110 klass_ptr = &klass->next;
>>>>>>>> 111 klass = *klass_ptr;
>>>>>>>> 112 found_tag = klass->klass_tag;
>>>>>>>>   113     }
>>>>>>>> 114
>>>>>>>> 115 // Tag not found? Ignore.
>>>>>>>> 116 if (found_tag != tag) {
>>>>>>>> 117 debugMonitorExit(deletedSignatureLock);
>>>>>>>> 118 return;
>>>>>>>>   119     }
>>>>>>>>
>>>>>>>>
>>>>>>>>   The code above can be simplified, so that the lines 101-105
>>>>>>>> are not
>>>>>>>> needed anymore.
>>>>>>>>   It can be something like this:
>>>>>>>>
>>>>>>>> // Scan linked-list.
>>>>>>>> while (klass != NULL && klass->klass_tag != tag) {
>>>>>>>> klass_ptr = &klass->next;
>>>>>>>> klass = *klass_ptr;
>>>>>>>>       }
>>>>>>>> if (klass == NULL || klass->klass_tag != tag) { // klass not
>>>>>>>> found - ignore.
>>>>>>>> debugMonitorExit(deletedSignatureLock);
>>>>>>>> return;
>>>>>>>>       }
>>>>>>>>
>>>>>>>> It will take more time when I get a chance to look at the rest.
>>>>>>>>
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>>> Serguei
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On 12/21/19 13:24, Roman Kennke wrote:
>>>>>>>>> Here comes an update that resolves some races that happen when
>>>>>>>>> disconnecting an agent. In particular, we need to take the lock on
>>>>>>>>> basically every operation, and also need to check whether or not
>>>>>>>>> class-tracking is active and return an appropriate result (e.g.
>>>>>>>>> an empty
>>>>>>>>> list) when we're not.
>>>>>>>>>
>>>>>>>>> Updated webrev:
>>>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.04/
>>>>>>>>>
>>>>>>>>> Thanks,
>>>>>>>>> Roman
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> So, here comes the O(1) implementation:
>>>>>>>>>>
>>>>>>>>>> - Whenever a class is 'prepared', it is registered with a tag,
>>>>>>>>>> and we
>>>>>>>>>> set-up a listener to get notified when it is unloaded.
>>>>>>>>>> - Prepared classes are kept in a datastructure that is a
>>>>>>>>>> table, which
>>>>>>>>>> each entry being the head of a linked-list of KlassNode*. The
>>>>>>>>>> table is
>>>>>>>>>> indexed by tag % slot-count, and then simply prepend the new
>>>>>>>>>> KlassNode*.
>>>>>>>>>> This is O(1) operation.
>>>>>>>>>> - When we get notified of unloading a class, we look up the
>>>>>>>>>> signature of
>>>>>>>>>> the reported tag in that table, and remember it in a bag. The
>>>>>>>>>> KlassNode*
>>>>>>>>>> is then unlinked from the table and deallocated. This is ~O(1)
>>>>>>>>>> operation
>>>>>>>>>> too, depending on the depth of the table. In my testcase which
>>>>>>>>>> hammered
>>>>>>>>>> the code with class-loads and unloads, I usually see depths of
>>>>>>>>>> like 2-3,
>>>>>>>>>> but not usually more. It should be ok.
>>>>>>>>>> - when processUnloads() gets called, we simply hand out that
>>>>>>>>>> bag, and
>>>>>>>>>> allocate a new one.
>>>>>>>>>> - I also added cleanup-code in classTrack_reset() to avoid
>>>>>>>>>> leaking the
>>>>>>>>>> signatures and KlassNode* etc when debug agent gets detached
>>>>>>>>>> and/or
>>>>>>>>>> re-attached (was missing before).
>>>>>>>>>> - I also added locks around data-structure-manipulation (was
>>>>>>>>>> missing
>>>>>>>>>> before).
>>>>>>>>>> - Also, I only activate this whole process when an actual
>>>>>>>>>> listener gets
>>>>>>>>>> registered on EI_GC_FINISH. This seems to happen right when
>>>>>>>>>> attaching a
>>>>>>>>>> jdb, not sure why jdb does that though. This may be something
>>>>>>>>>> to improve
>>>>>>>>>> in the future?
>>>>>>>>>>
>>>>>>>>>> In my tests, the performance of class-tracking itself looks
>>>>>>>>>> really good.
>>>>>>>>>> The bottleneck now is clearly actual synthesizing the
>>>>>>>>>> class-unload
>>>>>>>>>> events. I don't see how this can be helped when the debug
>>>>>>>>>> agent asks for it?
>>>>>>>>>>
>>>>>>>>>> Updated webrev:
>>>>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.03/
>>>>>>>>>>
>>>>>>>>>> Please let me know what you think of it.
>>>>>>>>>>
>>>>>>>>>> Thanks,
>>>>>>>>>> Roman
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> Alright, the perfectionist in me got me. I am implementing
>>>>>>>>>>> the even more
>>>>>>>>>>> efficient ~O(1) class tracking. Please hold off reviewing for
>>>>>>>>>>> now.
>>>>>>>>>>>
>>>>>>>>>>> Thanks,Roman
>>>>>>>>>>>
>>>>>>>>>>>   Hi Chris,
>>>>>>>>>>>>> I'll have a look at this, although it might not be for a
>>>>>>>>>>>>> few days. In
>>>>>>>>>>>>> the meantime, maybe you can describe your new
>>>>>>>>>>>>> implementation in
>>>>>>>>>>>>> classTrack.c so it's easier to look through the changes.
>>>>>>>>>>>> Sure.
>>>>>>>>>>>>
>>>>>>>>>>>> The purpose of this class-tracking is to be able to
>>>>>>>>>>>> determine the
>>>>>>>>>>>> signatures of unloaded classes when GC/class-unloading
>>>>>>>>>>>> happened, so that
>>>>>>>>>>>> we can generate the appropriate JDWP event.
>>>>>>>>>>>>
>>>>>>>>>>>> The current implementation does so by maintaining a table of
>>>>>>>>>>>> currently
>>>>>>>>>>>> prepared classes by building that table when classTrack is
>>>>>>>>>>>> initialized,
>>>>>>>>>>>> and then add new classes whenever a class gets loaded. When
>>>>>>>>>>>> unloading
>>>>>>>>>>>> occurs, that cache is rebuilt into a new table, and compared
>>>>>>>>>>>> with the
>>>>>>>>>>>> old table, and whatever is in the old, but not in the new
>>>>>>>>>>>> table gets
>>>>>>>>>>>> returned. The problem is that when GCs happen frequently
>>>>>>>>>>>> and/or many
>>>>>>>>>>>> classes get loaded+unloaded, this amounts to
>>>>>>>>>>>> O(classCount*gcCount)
>>>>>>>>>>>> complexity.
>>>>>>>>>>>>
>>>>>>>>>>>> The new implementation keeps a linked-list of prepared
>>>>>>>>>>>> classes, and also
>>>>>>>>>>>> tracks unloads via the listener cbTrackingObjectFree().
>>>>>>>>>>>> Whenever an
>>>>>>>>>>>> unload/GC occurs, the list of prepared classes is scanned,
>>>>>>>>>>>> and classes
>>>>>>>>>>>> that are also in the deletedTagBag are unlinked (thus
>>>>>>>>>>>> maintaining the
>>>>>>>>>>>> prepared-classes-list) and its signature put in the list
>>>>>>>>>>>> that gets returned.
>>>>>>>>>>>>
>>>>>>>>>>>> The implementation is not perfect. In order to determine
>>>>>>>>>>>> whether or not
>>>>>>>>>>>> a class is unloaded, it needs to scan the deletedTagBag.
>>>>>>>>>>>> That process is
>>>>>>>>>>>> therefore still O(unloadedClassCount). The assumption here
>>>>>>>>>>>> is that
>>>>>>>>>>>> unloadedClassCount << classCount. In my experiments this
>>>>>>>>>>>> seems to be
>>>>>>>>>>>> true, and also reasonable to expect.
>>>>>>>>>>>>
>>>>>>>>>>>> (I have some ideas how to improve the implementation to
>>>>>>>>>>>> ~O(1) but it
>>>>>>>>>>>> would be considerably more complex: have to maintain a
>>>>>>>>>>>> (hash)table that
>>>>>>>>>>>> maps tags -> KlassNode*, unlink them directly upon unload,
>>>>>>>>>>>> and build the
>>>>>>>>>>>> unloaded-signatures list there, but I don't currently see
>>>>>>>>>>>> that it's
>>>>>>>>>>>> worth the effort).
>>>>>>>>>>>>
>>>>>>>>>>>> In addition to all that, this process is only activated when
>>>>>>>>>>>> there's an
>>>>>>>>>>>> actual listener registered for EI_GC_FINISH.
>>>>>>>>>>>>
>>>>>>>>>>>> Thanks,
>>>>>>>>>>>> Roman
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> Chris
>>>>>>>>>>>>>
>>>>>>>>>>>>> On 12/18/19 5:05 AM, Roman Kennke wrote:
>>>>>>>>>>>>>> Hello all,
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Issue:
>>>>>>>>>>>>>> https://bugs.openjdk.java.net/browse/JDK-8227269
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I am proposing what amounts to a rewrite of classTrack.c.
>>>>>>>>>>>>>> It avoids
>>>>>>>>>>>>>> throwing away the class cache on GC, and instead keeps
>>>>>>>>>>>>>> track of
>>>>>>>>>>>>>> loaded/unloaded classes one-by-one.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> In addition to that, it avoids this whole dance until an
>>>>>>>>>>>>>> agent
>>>>>>>>>>>>>> registers interest in EI_GC_FINISH.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Webrev:
>>>>>>>>>>>>>> http://cr.openjdk.java.net/~rkennke/JDK-8227269/webrev.01/
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Testing: manual testing of provided test scenarios and
>>>>>>>>>>>>>> timing.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Eg with the testcase provided here:
>>>>>>>>>>>>>> https://bugzilla.redhat.com/show_bug.cgi?id=1751985
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I am getting those numbers:
>>>>>>>>>>>>>> unpatched: no debug: 84s with debug: 225s
>>>>>>>>>>>>>> patched:   no debug: 85s with debug: 95s
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I also tested successfully through jdk/submit repo
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Can I please get a review?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Thanks,
>>>>>>>>>>>>>> Roman
>>>>>>>>>>>>>>
>>
>>
> 
> 

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to