RFR: 8212879: Make JVMTI TagMap table concurrent [v5]

Serguei Spitsyn sspitsyn at openjdk.java.net
Wed Nov 4 02:19:00 UTC 2020


On Wed, 4 Nov 2020 00:08:10 GMT, Coleen Phillimore <coleenp at openjdk.org> wrote:

>> This change turns the HashTable that JVMTI uses for object tagging into a regular Hotspot hashtable - the one in hashtable.hpp with resizing and rehashing.   Instead of pointing directly to oops so that GC has to walk the table to follow oops and then to rehash the table, this table points to WeakHandle.  GC walks the backing OopStorages concurrently.
>> 
>> The hash function for the table is a hash of the lower 32 bits of the address.  A flag is set during GC (gc_notification if in a safepoint, and through a call to JvmtiTagMap::needs_processing()) so that the table is rehashed at the next use.
>> 
>> The gc_notification mechanism of weak oop processing is used to notify Jvmti to post ObjectFree events.  In concurrent GCs there can be a window of time between weak oop marking where the oop is unmarked, so dead (the phantom load in peek returns NULL) but the gc_notification hasn't been done yet.  In this window, a heap walk or GetObjectsWithTags call would not find an object before the ObjectFree event is posted.  This is dealt with in two ways:
>> 
>> 1. In the Heap walk, there's an unconditional table walk to post events if events are needed to post.
>> 2. For GetObjectWithTags, if a dead oop is found in the table and posting is required, we use the VM thread to post the event.
>> 
>> Event posting cannot be done in a JavaThread because the posting needs to be done while holding the table lock, so that the JvmtiEnv state doesn't change before posting is done.  ObjectFree callbacks are limited in what they can do as per the JVMTI Specification.  The allowed callbacks to the VM already have code to allow NonJava threads.
>> 
>> To avoid rehashing, I also tried to use object->identity_hash() but this breaks because entries can be added to the table during heapwalk, where the objects use marking.  The starting markWord is saved and restored.  Adding a hashcode during this operation makes restoring the former markWord (locked, inflated, etc) too complicated.  Plus we don't want all these objects to have hashcodes because locking operations after tagging would have to always use inflated locks.
>> 
>> Much of this change is to remove serial weak oop processing for the weakProcessor, ZGC and Shenandoah.  The GCs have been stress tested with jvmti code.
>> 
>> It has also been tested with tier1-6.
>> 
>> Thank you to Stefan, Erik and Kim for their help with this change.
>
> Coleen Phillimore has updated the pull request incrementally with one additional commit since the last revision:
> 
>   Code review comments from Kim and Albert.

Hi Coleen,

Wow, there are a lot of simplifications and code removal with this fix!
It looks great in general, just some nits below.
I also wanted to suggest renaming the 'set_needs_processing' to 'set_needs_rehashing'. :)

src/hotspot/share/prims/jvmtiTagMap.hpp:

Nit: Would it better to use a plural form 'post_dead_objects_on_vm_thread'? :
`+  void post_dead_object_on_vm_thread();`

src/hotspot/share/prims/jvmtiTagMap.cpp:

Nit: It'd be nice to add a short comment before the check_hashmap similar to L143 also explaining a difference (does check and post just for one env) with the check_hashmaps_for_heapwalk:
122 void JvmtiTagMap::check_hashmap(bool post_events) {
  . . .
143 // This checks for posting and rehashing and is called from the heap walks.
 144 void JvmtiTagMap::check_hashmaps_for_heapwalk() {

I'm just curious how this fragment was added. Did you get any failures in testing? :
1038   // skip if object is a dormant shared object whose mirror hasn't been loaded
1039   if (obj != NULL &&   obj->klass()->java_mirror() == NULL) {
1040     log_debug(cds, heap)("skipped dormant archived object " INTPTR_FORMAT " (%s)", p2i(obj),
1041                          obj->klass()->external_name());
1042     return;
1043   }

Nit: Can we rename this field to something like '_some_dead_found' or '_dead_found'? :
`1186   bool _some_dead;`

Nit: The lines 2997-3007 and 3009-3019 do the same but in different contexts. 
2996   if (!is_vm_thread) {
2997     if (num_dead_entries != 0) {
2998       JvmtiEnvIterator it;
2999       for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
3000         JvmtiTagMap* tag_map = env->tag_map_acquire();
3001         if (tag_map != NULL) {
3002           // Lock each hashmap from concurrent posting and cleaning
3003           tag_map->unlink_and_post_locked();
3004         }
3005       }
3006       // there's another callback for needs_rehashing
3007     }
3008   } else {
3009     assert(SafepointSynchronize::is_at_safepoint(), "must be");
3010     JvmtiEnvIterator it;
3011     for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
3012       JvmtiTagMap* tag_map = env->tag_map_acquire();
3013       if (tag_map != NULL && !tag_map->is_empty()) {
3014         if (num_dead_entries != 0) {
3015           tag_map->hashmap()->unlink_and_post(tag_map->env());
3016         }
3017         // Later GC code will relocate the oops, so defer rehashing until then.
3018         tag_map->_needs_rehashing = true;
3019       }
It feels like it can be refactored/simplified, at least, a little bit.
Is it possible to check and just return if (num_dead_entries == 0)?
If not, then, at least, it can be done the same way (except of locking).

Also, can we have just one (static?) lock for the whole gc_notification (not per JVMTI env/JvmtiTagMap)? How much do we win by locking per each env/JvmtiTagMap? Note, that in normal case there is just one agent. It is very rare to have multiple agents requesting object tagging and ObjectFree events. It seems, this can be refactored to more simple code with one function doing work in both contexts.

src/hotspot/share/utilities/hashtable.cpp:

Nit: Need space after the '{' :
  `+const int _small_table_sizes[] = {107, 1009, 2017, 4049, 5051, 10103, 20201, 40423 } ;`

src/hotspot/share/prims/jvmtiTagMapTable.cpp:

Nit: Extra space after assert:
  `119   assert (find(index, hash, obj) == NULL, "shouldn't already be present");`

Thanks,
Serguei

-------------

PR: https://git.openjdk.java.net/jdk/pull/967



More information about the build-dev mailing list