changeset 54511:fbfcebad8e66

8221393: ResolvedMethodTable too small for StackWalking applications Reviewed-by: coleenp, rehn
author stefank
date Wed, 10 Apr 2019 15:41:04 +0200
parents c97a91097f9f
children a84c46287f28
files src/hotspot/share/classfile/javaClasses.cpp src/hotspot/share/classfile/javaClasses.hpp src/hotspot/share/classfile/systemDictionary.cpp src/hotspot/share/gc/shared/weakProcessor.cpp src/hotspot/share/gc/shared/weakProcessor.inline.hpp src/hotspot/share/gc/shared/weakProcessorPhases.cpp src/hotspot/share/gc/shared/weakProcessorPhases.hpp src/hotspot/share/gc/z/zRootsIterator.cpp src/hotspot/share/gc/z/zRootsIterator.hpp src/hotspot/share/oops/weakHandle.cpp src/hotspot/share/oops/weakHandle.hpp src/hotspot/share/prims/resolvedMethodTable.cpp src/hotspot/share/prims/resolvedMethodTable.hpp src/hotspot/share/prims/whitebox.cpp src/hotspot/share/runtime/mutexLocker.cpp src/hotspot/share/runtime/mutexLocker.hpp src/hotspot/share/runtime/serviceThread.cpp test/hotspot/jtreg/runtime/MemberName/MemberNameLeak.java test/hotspot/jtreg/runtime/testlibrary/ClassWithManyMethodsClassLoader.java test/lib/sun/hotspot/WhiteBox.java
diffstat 20 files changed, 705 insertions(+), 298 deletions(-) [+]
line wrap: on
line diff
--- a/src/hotspot/share/classfile/javaClasses.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/classfile/javaClasses.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -3635,23 +3635,48 @@
   resolved_method->address_field_put(_vmtarget_offset, (address)m);
 }
 
+void java_lang_invoke_ResolvedMethodName::set_vmholder(oop resolved_method, oop holder) {
+  assert(is_instance(resolved_method), "wrong type");
+  resolved_method->obj_field_put(_vmholder_offset, holder);
+}
+
 oop java_lang_invoke_ResolvedMethodName::find_resolved_method(const methodHandle& m, TRAPS) {
+  const Method* method = m();
+
   // lookup ResolvedMethod oop in the table, or create a new one and intern it
-  oop resolved_method = ResolvedMethodTable::find_method(m());
-  if (resolved_method == NULL) {
-    InstanceKlass* k = SystemDictionary::ResolvedMethodName_klass();
-    if (!k->is_initialized()) {
-      k->initialize(CHECK_NULL);
-    }
-    oop new_resolved_method = k->allocate_instance(CHECK_NULL);
-    new_resolved_method->address_field_put(_vmtarget_offset, (address)m());
-    // Add a reference to the loader (actually mirror because unsafe anonymous classes will not have
-    // distinct loaders) to ensure the metadata is kept alive.
-    // This mirror may be different than the one in clazz field.
-    new_resolved_method->obj_field_put(_vmholder_offset, m->method_holder()->java_mirror());
-    resolved_method = ResolvedMethodTable::add_method(m, Handle(THREAD, new_resolved_method));
+  oop resolved_method = ResolvedMethodTable::find_method(method);
+  if (resolved_method != NULL) {
+    return resolved_method;
   }
-  return resolved_method;
+
+  InstanceKlass* k = SystemDictionary::ResolvedMethodName_klass();
+  if (!k->is_initialized()) {
+    k->initialize(CHECK_NULL);
+  }
+
+  oop new_resolved_method = k->allocate_instance(CHECK_NULL);
+
+  NoSafepointVerifier nsv;
+
+  if (method->is_old()) {
+    method = (method->is_deleted()) ? Universe::throw_no_such_method_error() :
+                                      method->get_new_method();
+  }
+
+  InstanceKlass* holder = method->method_holder();
+
+  set_vmtarget(new_resolved_method, const_cast<Method*>(method));
+  // Add a reference to the loader (actually mirror because unsafe anonymous classes will not have
+  // distinct loaders) to ensure the metadata is kept alive.
+  // This mirror may be different than the one in clazz field.
+  set_vmholder(new_resolved_method, holder->java_mirror());
+
+  // Set flag in class to indicate this InstanceKlass has entries in the table
+  // to avoid walking table during redefinition if none of the redefined classes
+  // have any membernames in the table.
+  holder->set_has_resolved_methods();
+
+  return ResolvedMethodTable::add_method(method, Handle(THREAD, new_resolved_method));
 }
 
 oop java_lang_invoke_LambdaForm::vmentry(oop lform) {
--- a/src/hotspot/share/classfile/javaClasses.hpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/classfile/javaClasses.hpp	Wed Apr 10 15:41:04 2019 +0200
@@ -1063,6 +1063,8 @@
   static Method* vmtarget(oop resolved_method);
   static void set_vmtarget(oop resolved_method, Method* method);
 
+  static void set_vmholder(oop resolved_method, oop holder);
+
   // find or create resolved member name
   static oop find_resolved_method(const methodHandle& m, TRAPS);
 
--- a/src/hotspot/share/classfile/systemDictionary.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/classfile/systemDictionary.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -68,7 +68,6 @@
 #include "oops/symbol.hpp"
 #include "oops/typeArrayKlass.hpp"
 #include "prims/jvmtiExport.hpp"
-#include "prims/resolvedMethodTable.hpp"
 #include "prims/methodHandles.hpp"
 #include "runtime/arguments.hpp"
 #include "runtime/biasedLocking.hpp"
@@ -1836,8 +1835,6 @@
   }
 
   GCTraceTime(Debug, gc, phases) t("Trigger cleanups", gc_timer);
-  // Trigger cleaning the ResolvedMethodTable even if no unloading occurred.
-  ResolvedMethodTable::trigger_cleanup();
 
   if (unloading_occurred) {
     SymbolTable::trigger_cleanup();
--- a/src/hotspot/share/gc/shared/weakProcessor.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/gc/shared/weakProcessor.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -31,22 +31,33 @@
 #include "gc/shared/weakProcessorPhaseTimes.hpp"
 #include "memory/allocation.inline.hpp"
 #include "memory/iterator.hpp"
+#include "prims/resolvedMethodTable.hpp"
 #include "runtime/globals.hpp"
 #include "utilities/macros.hpp"
 
+template <typename Container>
+class OopsDoAndReportCounts {
+public:
+  void operator()(BoolObjectClosure* is_alive, OopClosure* keep_alive, WeakProcessorPhase phase) {
+    Container::reset_dead_counter();
+
+    CountingSkippedIsAliveClosure<BoolObjectClosure, OopClosure> cl(is_alive, keep_alive);
+    WeakProcessorPhases::oop_storage(phase)->oops_do(&cl);
+
+    Container::inc_dead_counter(cl.num_dead() + cl.num_skipped());
+    Container::finish_dead_counter();
+  }
+};
+
 void WeakProcessor::weak_oops_do(BoolObjectClosure* is_alive, OopClosure* keep_alive) {
   FOR_EACH_WEAK_PROCESSOR_PHASE(phase) {
     if (WeakProcessorPhases::is_serial(phase)) {
       WeakProcessorPhases::processor(phase)(is_alive, keep_alive);
     } else {
       if (WeakProcessorPhases::is_stringtable(phase)) {
-        StringTable::reset_dead_counter();
-
-        CountingSkippedIsAliveClosure<BoolObjectClosure, OopClosure> cl(is_alive, keep_alive);
-        WeakProcessorPhases::oop_storage(phase)->oops_do(&cl);
-
-        StringTable::inc_dead_counter(cl.num_dead() + cl.num_skipped());
-        StringTable::finish_dead_counter();
+        OopsDoAndReportCounts<StringTable>()(is_alive, keep_alive, phase);
+      } else if (WeakProcessorPhases::is_resolved_method_table(phase)){
+        OopsDoAndReportCounts<ResolvedMethodTable>()(is_alive, keep_alive, phase);
       } else {
         WeakProcessorPhases::oop_storage(phase)->weak_oops_do(is_alive, keep_alive);
       }
@@ -104,6 +115,7 @@
     new (states++) StorageState(storage, _nworkers);
   }
   StringTable::reset_dead_counter();
+  ResolvedMethodTable::reset_dead_counter();
 }
 
 WeakProcessor::Task::Task(uint nworkers) :
@@ -134,6 +146,7 @@
     FREE_C_HEAP_ARRAY(StorageState, _storage_states);
   }
   StringTable::finish_dead_counter();
+  ResolvedMethodTable::finish_dead_counter();
 }
 
 void WeakProcessor::GangTask::work(uint worker_id) {
--- a/src/hotspot/share/gc/shared/weakProcessor.inline.hpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/gc/shared/weakProcessor.inline.hpp	Wed Apr 10 15:41:04 2019 +0200
@@ -32,6 +32,7 @@
 #include "gc/shared/weakProcessorPhases.hpp"
 #include "gc/shared/weakProcessorPhaseTimes.hpp"
 #include "gc/shared/workgroup.hpp"
+#include "prims/resolvedMethodTable.hpp"
 #include "utilities/debug.hpp"
 
 class BoolObjectClosure;
@@ -115,6 +116,9 @@
       if (WeakProcessorPhases::is_stringtable(phase)) {
         StringTable::inc_dead_counter(cl.num_dead() + cl.num_skipped());
       }
+      if (WeakProcessorPhases::is_resolved_method_table(phase)) {
+        ResolvedMethodTable::inc_dead_counter(cl.num_dead() + cl.num_skipped());
+      }
     }
   }
 
--- a/src/hotspot/share/gc/shared/weakProcessorPhases.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/gc/shared/weakProcessorPhases.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -26,6 +26,7 @@
 #include "classfile/stringTable.hpp"
 #include "classfile/systemDictionary.hpp"
 #include "gc/shared/weakProcessorPhases.hpp"
+#include "prims/resolvedMethodTable.hpp"
 #include "runtime/jniHandles.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/macros.hpp"
@@ -80,6 +81,7 @@
   JFR_ONLY(case jfr: return "JFR weak processing";)
   case jni: return "JNI weak processing";
   case stringtable: return "StringTable weak processing";
+  case resolved_method_table: return "ResolvedMethodTable weak processing";
   case vm: return "VM weak processing";
   default:
     ShouldNotReachHere();
@@ -101,6 +103,7 @@
   switch (phase) {
   case jni: return JNIHandles::weak_global_handles();
   case stringtable: return StringTable::weak_storage();
+  case resolved_method_table: return ResolvedMethodTable::weak_storage();
   case vm: return SystemDictionary::vm_weak_oop_storage();
   default:
     ShouldNotReachHere();
@@ -111,3 +114,7 @@
 bool WeakProcessorPhases::is_stringtable(Phase phase) {
   return phase == stringtable;
 }
+
+bool WeakProcessorPhases::is_resolved_method_table(Phase phase) {
+  return phase == resolved_method_table;
+}
--- a/src/hotspot/share/gc/shared/weakProcessorPhases.hpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/gc/shared/weakProcessorPhases.hpp	Wed Apr 10 15:41:04 2019 +0200
@@ -45,6 +45,7 @@
     // OopStorage phases.
     jni,
     stringtable,
+    resolved_method_table,
     vm
   };
 
@@ -68,6 +69,7 @@
   static OopStorage* oop_storage(Phase phase); // Precondition: is_oop_storage(phase)
 
   static bool is_stringtable(Phase phase);
+  static bool is_resolved_method_table(Phase phase);
 };
 
 typedef WeakProcessorPhases::Phase WeakProcessorPhase;
--- a/src/hotspot/share/gc/z/zRootsIterator.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/gc/z/zRootsIterator.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -41,6 +41,7 @@
 #include "memory/resourceArea.hpp"
 #include "memory/universe.hpp"
 #include "prims/jvmtiExport.hpp"
+#include "prims/resolvedMethodTable.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/jniHandles.hpp"
 #include "runtime/thread.hpp"
@@ -80,6 +81,7 @@
 static const ZStatSubPhase ZSubPhaseConcurrentWeakRootsVMWeakHandles("Concurrent Weak Roots VMWeakHandles");
 static const ZStatSubPhase ZSubPhaseConcurrentWeakRootsJNIWeakHandles("Concurrent Weak Roots JNIWeakHandles");
 static const ZStatSubPhase ZSubPhaseConcurrentWeakRootsStringTable("Concurrent Weak Roots StringTable");
+static const ZStatSubPhase ZSubPhaseConcurrentWeakRootsResolvedMethodTable("Concurrent Weak Roots ResolvedMethodTable");
 
 template <typename T, void (T::*F)(ZRootsIteratorClosure*)>
 ZSerialOopsDo<T, F>::ZSerialOopsDo(T* iter) :
@@ -341,14 +343,18 @@
     _vm_weak_handles_iter(SystemDictionary::vm_weak_oop_storage()),
     _jni_weak_handles_iter(JNIHandles::weak_global_handles()),
     _string_table_iter(StringTable::weak_storage()),
+    _resolved_method_table_iter(ResolvedMethodTable::weak_storage()),
     _vm_weak_handles(this),
     _jni_weak_handles(this),
-    _string_table(this) {
+    _string_table(this),
+    _resolved_method_table(this) {
   StringTable::reset_dead_counter();
+  ResolvedMethodTable::reset_dead_counter();
 }
 
 ZConcurrentWeakRootsIterator::~ZConcurrentWeakRootsIterator() {
   StringTable::finish_dead_counter();
+  ResolvedMethodTable::finish_dead_counter();
 }
 
 void ZConcurrentWeakRootsIterator::do_vm_weak_handles(ZRootsIteratorClosure* cl) {
@@ -361,18 +367,19 @@
   _jni_weak_handles_iter.oops_do(cl);
 }
 
-class ZStringTableDeadCounterClosure : public ZRootsIteratorClosure  {
+template <class Container>
+class ZDeadCounterClosure : public ZRootsIteratorClosure  {
 private:
   ZRootsIteratorClosure* const _cl;
   size_t                       _ndead;
 
 public:
-  ZStringTableDeadCounterClosure(ZRootsIteratorClosure* cl) :
+  ZDeadCounterClosure(ZRootsIteratorClosure* cl) :
       _cl(cl),
       _ndead(0) {}
 
-  ~ZStringTableDeadCounterClosure() {
-    StringTable::inc_dead_counter(_ndead);
+  ~ZDeadCounterClosure() {
+    Container::inc_dead_counter(_ndead);
   }
 
   virtual void do_oop(oop* p) {
@@ -389,15 +396,22 @@
 
 void ZConcurrentWeakRootsIterator::do_string_table(ZRootsIteratorClosure* cl) {
   ZStatTimer timer(ZSubPhaseConcurrentWeakRootsStringTable);
-  ZStringTableDeadCounterClosure counter_cl(cl);
+  ZDeadCounterClosure<StringTable> counter_cl(cl);
   _string_table_iter.oops_do(&counter_cl);
 }
 
+void ZConcurrentWeakRootsIterator::do_resolved_method_table(ZRootsIteratorClosure* cl) {
+  ZStatTimer timer(ZSubPhaseConcurrentWeakRootsResolvedMethodTable);
+  ZDeadCounterClosure<ResolvedMethodTable> counter_cl(cl);
+  _resolved_method_table_iter.oops_do(&counter_cl);
+}
+
 void ZConcurrentWeakRootsIterator::oops_do(ZRootsIteratorClosure* cl) {
   ZStatTimer timer(ZSubPhaseConcurrentWeakRoots);
   _vm_weak_handles.oops_do(cl);
   _jni_weak_handles.oops_do(cl);
   _string_table.oops_do(cl);
+  _resolved_method_table.oops_do(cl);
 }
 
 ZThreadRootsIterator::ZThreadRootsIterator() :
--- a/src/hotspot/share/gc/z/zRootsIterator.hpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/gc/z/zRootsIterator.hpp	Wed Apr 10 15:41:04 2019 +0200
@@ -149,14 +149,17 @@
   ZOopStorageIterator _vm_weak_handles_iter;
   ZOopStorageIterator _jni_weak_handles_iter;
   ZOopStorageIterator _string_table_iter;
+  ZOopStorageIterator _resolved_method_table_iter;
 
   void do_vm_weak_handles(ZRootsIteratorClosure* cl);
   void do_jni_weak_handles(ZRootsIteratorClosure* cl);
   void do_string_table(ZRootsIteratorClosure* cl);
+  void do_resolved_method_table(ZRootsIteratorClosure* cl);
 
-  ZParallelOopsDo<ZConcurrentWeakRootsIterator, &ZConcurrentWeakRootsIterator::do_vm_weak_handles>  _vm_weak_handles;
-  ZParallelOopsDo<ZConcurrentWeakRootsIterator, &ZConcurrentWeakRootsIterator::do_jni_weak_handles> _jni_weak_handles;
-  ZParallelOopsDo<ZConcurrentWeakRootsIterator, &ZConcurrentWeakRootsIterator::do_string_table>     _string_table;
+  ZParallelOopsDo<ZConcurrentWeakRootsIterator, &ZConcurrentWeakRootsIterator::do_vm_weak_handles>       _vm_weak_handles;
+  ZParallelOopsDo<ZConcurrentWeakRootsIterator, &ZConcurrentWeakRootsIterator::do_jni_weak_handles>      _jni_weak_handles;
+  ZParallelOopsDo<ZConcurrentWeakRootsIterator, &ZConcurrentWeakRootsIterator::do_string_table>          _string_table;
+  ZParallelOopsDo<ZConcurrentWeakRootsIterator, &ZConcurrentWeakRootsIterator::do_resolved_method_table> _resolved_method_table;
 
 public:
   ZConcurrentWeakRootsIterator();
--- a/src/hotspot/share/oops/weakHandle.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/oops/weakHandle.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -29,6 +29,7 @@
 #include "oops/access.inline.hpp"
 #include "oops/oop.hpp"
 #include "oops/weakHandle.inline.hpp"
+#include "prims/resolvedMethodTable.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/ostream.hpp"
 
@@ -40,6 +41,10 @@
   return StringTable::weak_storage();
 }
 
+template <> OopStorage* WeakHandle<vm_resolved_method_table_data>::get_storage() {
+  return ResolvedMethodTable::weak_storage();
+}
+
 template <WeakHandleType T>
 WeakHandle<T> WeakHandle<T>::create(Handle obj) {
   assert(obj() != NULL, "no need to create weak null oop");
@@ -74,4 +79,4 @@
 // Provide instantiation.
 template class WeakHandle<vm_class_loader_data>;
 template class WeakHandle<vm_string_table_data>;
-
+template class WeakHandle<vm_resolved_method_table_data>;
--- a/src/hotspot/share/oops/weakHandle.hpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/oops/weakHandle.hpp	Wed Apr 10 15:41:04 2019 +0200
@@ -39,7 +39,7 @@
 // This is the vm version of jweak but has different GC lifetimes and policies,
 // depending on the type.
 
-enum WeakHandleType { vm_class_loader_data, vm_string_table_data };
+enum WeakHandleType { vm_class_loader_data, vm_string_table_data, vm_resolved_method_table_data };
 
 template <WeakHandleType T>
 class WeakHandle {
@@ -64,6 +64,4 @@
   void print_on(outputStream* st) const;
 };
 
-typedef WeakHandle<vm_class_loader_data> ClassLoaderWeakHandle;
-
 #endif // SHARE_OOPS_WEAKHANDLE_HPP
--- a/src/hotspot/share/prims/resolvedMethodTable.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/prims/resolvedMethodTable.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -24,223 +24,422 @@
 
 #include "precompiled.hpp"
 #include "classfile/javaClasses.hpp"
+#include "gc/shared/oopStorage.inline.hpp"
 #include "logging/log.hpp"
 #include "memory/allocation.hpp"
 #include "memory/resourceArea.hpp"
 #include "oops/access.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "oops/method.hpp"
-#include "oops/symbol.hpp"
 #include "oops/weakHandle.inline.hpp"
 #include "prims/resolvedMethodTable.hpp"
 #include "runtime/handles.inline.hpp"
+#include "runtime/interfaceSupport.inline.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "runtime/safepointVerifiers.hpp"
-#include "utilities/hashtable.inline.hpp"
+#include "runtime/timerTrace.hpp"
+#include "utilities/concurrentHashTable.inline.hpp"
+#include "utilities/concurrentHashTableTasks.inline.hpp"
 #include "utilities/macros.hpp"
 
+// 2^24 is max size
+static const size_t END_SIZE = 24;
+// If a chain gets to 32 something might be wrong
+static const size_t GROW_HINT = 32;
 
-oop ResolvedMethodEntry::object() {
-  return literal().resolve();
-}
+static const size_t ResolvedMethodTableSizeLog = 10;
 
-oop ResolvedMethodEntry::object_no_keepalive() {
-  // The AS_NO_KEEPALIVE peeks at the oop without keeping it alive.
-  // This is dangerous in general but is okay if the loaded oop does
-  // not leak out past a thread transition where a safepoint can happen.
-  // A subsequent oop_load without AS_NO_KEEPALIVE (the object() accessor)
-  // keeps the oop alive before doing so.
-  return literal().peek();
-}
-
-ResolvedMethodTable::ResolvedMethodTable()
-  : Hashtable<ClassLoaderWeakHandle, mtClass>(_table_size, sizeof(ResolvedMethodEntry)) { }
-
-oop ResolvedMethodTable::lookup(int index, unsigned int hash, Method* method) {
-  assert_locked_or_safepoint(ResolvedMethodTable_lock);
-  for (ResolvedMethodEntry* p = bucket(index); p != NULL; p = p->next()) {
-    if (p->hash() == hash) {
-
-      // Peek the object to check if it is the right target.
-      oop target = p->object_no_keepalive();
-
-      // The method is in the table as a target already
-      if (target != NULL && java_lang_invoke_ResolvedMethodName::vmtarget(target) == method) {
-        ResourceMark rm;
-        log_debug(membername, table) ("ResolvedMethod entry found for %s index %d",
-                                       method->name_and_sig_as_C_string(), index);
-        // The object() accessor makes sure the target object is kept alive before
-        // leaking out.
-        return p->object();
-      }
-    }
-  }
-  return NULL;
-}
-
-unsigned int ResolvedMethodTable::compute_hash(Method* method) {
+unsigned int method_hash(const Method* method) {
   unsigned int name_hash = method->name()->identity_hash();
   unsigned int signature_hash = method->signature()->identity_hash();
   return name_hash ^ signature_hash;
 }
 
+class ResolvedMethodTableConfig : public ResolvedMethodTableHash::BaseConfig {
+ private:
+ public:
+  static uintx get_hash(WeakHandle<vm_resolved_method_table_data> const& value,
+                        bool* is_dead) {
+    EXCEPTION_MARK;
+    oop val_oop = value.peek();
+    if (val_oop == NULL) {
+      *is_dead = true;
+      return 0;
+    }
+    *is_dead = false;
+    Method* method = java_lang_invoke_ResolvedMethodName::vmtarget(val_oop);
+    return method_hash(method);
+  }
 
-oop ResolvedMethodTable::lookup(Method* method) {
-  unsigned int hash = compute_hash(method);
-  int index = hash_to_index(hash);
-  return lookup(index, hash, method);
+  // We use default allocation/deallocation but counted
+  static void* allocate_node(size_t size, WeakHandle<vm_resolved_method_table_data> const& value) {
+    ResolvedMethodTable::item_added();
+    return ResolvedMethodTableHash::BaseConfig::allocate_node(size, value);
+  }
+  static void free_node(void* memory, WeakHandle<vm_resolved_method_table_data> const& value) {
+    value.release();
+    ResolvedMethodTableHash::BaseConfig::free_node(memory, value);
+    ResolvedMethodTable::item_removed();
+  }
+};
+
+ResolvedMethodTableHash* ResolvedMethodTable::_local_table           = NULL;
+size_t                   ResolvedMethodTable::_current_size          = (size_t)1 << ResolvedMethodTableSizeLog;
+
+OopStorage*              ResolvedMethodTable::_weak_handles          = NULL;
+
+volatile bool            ResolvedMethodTable::_has_work              = false;
+volatile size_t          ResolvedMethodTable::_items_count           = 0;
+volatile size_t          ResolvedMethodTable::_uncleaned_items_count = 0;
+
+void ResolvedMethodTable::create_table() {
+  _local_table  = new ResolvedMethodTableHash(ResolvedMethodTableSizeLog, END_SIZE, GROW_HINT);
+  _weak_handles = new OopStorage("ResolvedMethodTable weak",
+                                 ResolvedMethodTableWeakAlloc_lock,
+                                 ResolvedMethodTableWeakActive_lock);
+  log_trace(membername, table)("Start size: " SIZE_FORMAT " (" SIZE_FORMAT ")",
+                               _current_size, ResolvedMethodTableSizeLog);
 }
 
-oop ResolvedMethodTable::basic_add(Method* method, Handle rmethod_name) {
-  assert_locked_or_safepoint(ResolvedMethodTable_lock);
+size_t ResolvedMethodTable::table_size() {
+  return (size_t)1 << _local_table->get_size_log2(Thread::current());
+}
 
-  unsigned int hash = compute_hash(method);
-  int index = hash_to_index(hash);
+class ResolvedMethodTableLookup : StackObj {
+ private:
+  Thread*       _thread;
+  uintx         _hash;
+  const Method* _method;
+  Handle        _found;
 
-  // One was added while aquiring the lock
-  oop entry = lookup(index, hash, method);
-  if (entry != NULL) {
-    return entry;
+ public:
+  ResolvedMethodTableLookup(Thread* thread, uintx hash, const Method* key)
+    : _thread(thread), _hash(hash), _method(key) {
+  }
+  uintx get_hash() const {
+    return _hash;
+  }
+  bool equals(WeakHandle<vm_resolved_method_table_data>* value, bool* is_dead) {
+    oop val_oop = value->peek();
+    if (val_oop == NULL) {
+      // dead oop, mark this hash dead for cleaning
+      *is_dead = true;
+      return false;
+    }
+    bool equals = _method == java_lang_invoke_ResolvedMethodName::vmtarget(val_oop);
+    if (!equals) {
+      return false;
+    }
+    // Need to resolve weak handle and Handleize through possible safepoint.
+    _found = Handle(_thread, value->resolve());
+    return true;
+  }
+};
+
+
+class ResolvedMethodGet : public StackObj {
+  Thread*       _thread;
+  const Method* _method;
+  Handle        _return;
+public:
+  ResolvedMethodGet(Thread* thread, const Method* method) : _thread(thread), _method(method) {}
+  void operator()(WeakHandle<vm_resolved_method_table_data>* val) {
+    oop result = val->resolve();
+    assert(result != NULL, "Result should be reachable");
+    _return = Handle(_thread, result);
+    log_get();
+  }
+  oop get_res_oop() {
+    return _return();
+  }
+  void log_get() {
+    LogTarget(Trace, membername, table) log;
+    if (log.is_enabled()) {
+      ResourceMark rm;
+      log.print("ResolvedMethod entry found for %s",
+                _method->name_and_sig_as_C_string());
+    }
+  }
+};
+
+oop ResolvedMethodTable::find_method(const Method* method) {
+  Thread* thread = Thread::current();
+
+  ResolvedMethodTableLookup lookup(thread, method_hash(method), method);
+  ResolvedMethodGet rmg(thread, method);
+  _local_table->get(thread, lookup, rmg);
+
+  return rmg.get_res_oop();
+}
+
+static void log_insert(const Method* method) {
+  LogTarget(Debug, membername, table) log;
+  if (log.is_enabled()) {
+    ResourceMark rm;
+    log_debug(membername, table) ("ResolvedMethod entry added for %s",
+                                  method->name_and_sig_as_C_string());
+  }
+}
+
+oop ResolvedMethodTable::add_method(const Method* method, Handle rmethod_name) {
+  Thread* thread = Thread::current();
+
+  ResolvedMethodTableLookup lookup(thread, method_hash(method), method);
+  ResolvedMethodGet rmg(thread, method);
+
+  while (true) {
+    if (_local_table->get(thread, lookup, rmg)) {
+      return rmg.get_res_oop();
+    }
+    WeakHandle<vm_resolved_method_table_data> wh = WeakHandle<vm_resolved_method_table_data>::create(rmethod_name);
+    // The hash table takes ownership of the WeakHandle, even if it's not inserted.
+    if (_local_table->insert(thread, lookup, wh)) {
+      log_insert(method);
+      return wh.resolve();
+    }
   }
 
-  ClassLoaderWeakHandle w = ClassLoaderWeakHandle::create(rmethod_name);
-  ResolvedMethodEntry* p = (ResolvedMethodEntry*) Hashtable<ClassLoaderWeakHandle, mtClass>::new_entry(hash, w);
-  Hashtable<ClassLoaderWeakHandle, mtClass>::add_entry(index, p);
-  ResourceMark rm;
-  log_debug(membername, table) ("ResolvedMethod entry added for %s index %d",
-                                 method->name_and_sig_as_C_string(), index);
   return rmethod_name();
 }
 
-ResolvedMethodTable* ResolvedMethodTable::_the_table = NULL;
-
-oop ResolvedMethodTable::find_method(Method* method) {
-  MutexLocker ml(ResolvedMethodTable_lock);
-  oop entry = _the_table->lookup(method);
-  return entry;
+void ResolvedMethodTable::item_added() {
+  Atomic::inc(&_items_count);
 }
 
-oop ResolvedMethodTable::add_method(const methodHandle& m, Handle resolved_method_name) {
-  MutexLocker ml(ResolvedMethodTable_lock);
-  DEBUG_ONLY(NoSafepointVerifier nsv);
+void ResolvedMethodTable::item_removed() {
+  Atomic::dec(&_items_count);
+  log_trace(membername, table) ("ResolvedMethod entry removed");
+}
 
-  Method* method = m();
-  // Check if method has been redefined while taking out ResolvedMethodTable_lock, if so
-  // use new method in the ResolvedMethodName.  The old method won't be deallocated
-  // yet because it's passed in as a Handle.
-  if (method->is_old()) {
-    method = (method->is_deleted()) ? Universe::throw_no_such_method_error() :
-                                      method->get_new_method();
-    java_lang_invoke_ResolvedMethodName::set_vmtarget(resolved_method_name(), method);
+bool ResolvedMethodTable::has_work() {
+  return _has_work;
+}
+
+OopStorage* ResolvedMethodTable::weak_storage() {
+  return _weak_handles;
+}
+
+double ResolvedMethodTable::get_load_factor() {
+  return (double)_items_count/_current_size;
+}
+
+double ResolvedMethodTable::get_dead_factor() {
+  return (double)_uncleaned_items_count/_current_size;
+}
+
+static const double PREF_AVG_LIST_LEN = 2.0;
+// If we have as many dead items as 50% of the number of bucket
+static const double CLEAN_DEAD_HIGH_WATER_MARK = 0.5;
+
+void ResolvedMethodTable::check_concurrent_work() {
+  if (_has_work) {
+    return;
   }
 
-  // Set flag in class to indicate this InstanceKlass has entries in the table
-  // to avoid walking table during redefinition if none of the redefined classes
-  // have any membernames in the table.
-  method->method_holder()->set_has_resolved_methods();
-
-  return _the_table->basic_add(method, resolved_method_name);
+  double load_factor = get_load_factor();
+  double dead_factor = get_dead_factor();
+  // We should clean/resize if we have more dead than alive,
+  // more items than preferred load factor or
+  // more dead items than water mark.
+  if ((dead_factor > load_factor) ||
+      (load_factor > PREF_AVG_LIST_LEN) ||
+      (dead_factor > CLEAN_DEAD_HIGH_WATER_MARK)) {
+    log_debug(membername, table)("Concurrent work triggered, live factor: %g dead factor: %g",
+                                 load_factor, dead_factor);
+    trigger_concurrent_work();
+  }
 }
 
-// Removing entries
-int ResolvedMethodTable::_total_oops_removed = 0;
-
-// There are no dead entries at start
-bool ResolvedMethodTable::_dead_entries = false;
-
-void ResolvedMethodTable::trigger_cleanup() {
+void ResolvedMethodTable::trigger_concurrent_work() {
   MutexLockerEx ml(Service_lock, Mutex::_no_safepoint_check_flag);
-  _dead_entries = true;
+  _has_work = true;
   Service_lock->notify_all();
 }
 
-// Serially invoke removed unused oops from the table.
-// This is done by the ServiceThread after being notified on class unloading
-void ResolvedMethodTable::unlink() {
-  MutexLocker ml(ResolvedMethodTable_lock);
-  int _oops_removed = 0;
-  int _oops_counted = 0;
-  for (int i = 0; i < _the_table->table_size(); ++i) {
-    ResolvedMethodEntry** p = _the_table->bucket_addr(i);
-    ResolvedMethodEntry* entry = _the_table->bucket(i);
-    while (entry != NULL) {
-      _oops_counted++;
-      oop l = entry->object_no_keepalive();
-      if (l != NULL) {
-        p = entry->next_addr();
-      } else {
-        // Entry has been removed.
-        _oops_removed++;
-        if (log_is_enabled(Debug, membername, table)) {
-          log_debug(membername, table) ("ResolvedMethod entry removed for index %d", i);
-        }
-        entry->literal().release();
-        *p = entry->next();
-        _the_table->free_entry(entry);
+void ResolvedMethodTable::do_concurrent_work(JavaThread* jt) {
+  _has_work = false;
+  double load_factor = get_load_factor();
+  log_debug(membername, table)("Concurrent work, live factor: %g", load_factor);
+  // We prefer growing, since that also removes dead items
+  if (load_factor > PREF_AVG_LIST_LEN && !_local_table->is_max_size_reached()) {
+    grow(jt);
+  } else {
+    clean_dead_entries(jt);
+  }
+}
+
+void ResolvedMethodTable::grow(JavaThread* jt) {
+  ResolvedMethodTableHash::GrowTask gt(_local_table);
+  if (!gt.prepare(jt)) {
+    return;
+  }
+  log_trace(membername, table)("Started to grow");
+  {
+    TraceTime timer("Grow", TRACETIME_LOG(Debug, membername, table, perf));
+    while (gt.do_task(jt)) {
+      gt.pause(jt);
+      {
+        ThreadBlockInVM tbivm(jt);
       }
-      // get next entry
-      entry = (ResolvedMethodEntry*)HashtableEntry<ClassLoaderWeakHandle, mtClass>::make_ptr(*p);
+      gt.cont(jt);
     }
   }
-  log_debug(membername, table) ("ResolvedMethod entries counted %d removed %d",
-                                _oops_counted, _oops_removed);
-  _total_oops_removed += _oops_removed;
-  _dead_entries = false;
+  gt.done(jt);
+  _current_size = table_size();
+  log_info(membername, table)("Grown to size:" SIZE_FORMAT, _current_size);
 }
 
-#ifndef PRODUCT
-void ResolvedMethodTable::print() {
-  MutexLocker ml(ResolvedMethodTable_lock);
-  for (int i = 0; i < table_size(); ++i) {
-    ResolvedMethodEntry* entry = bucket(i);
-    while (entry != NULL) {
-      tty->print("%d : ", i);
-      oop rmethod_name = entry->object_no_keepalive();
-      if (rmethod_name != NULL) {
-        rmethod_name->print();
-        Method* m = (Method*)java_lang_invoke_ResolvedMethodName::vmtarget(rmethod_name);
-        m->print();
-      }
-      entry = entry->next();
+struct ResolvedMethodTableDoDelete : StackObj {
+  void operator()(WeakHandle<vm_resolved_method_table_data>* val) {
+    /* do nothing */
+  }
+};
+
+struct ResolvedMethodTableDeleteCheck : StackObj {
+  long _count;
+  long _item;
+  ResolvedMethodTableDeleteCheck() : _count(0), _item(0) {}
+  bool operator()(WeakHandle<vm_resolved_method_table_data>* val) {
+    ++_item;
+    oop tmp = val->peek();
+    if (tmp == NULL) {
+      ++_count;
+      return true;
+    } else {
+      return false;
     }
   }
+};
+
+void ResolvedMethodTable::clean_dead_entries(JavaThread* jt) {
+  ResolvedMethodTableHash::BulkDeleteTask bdt(_local_table);
+  if (!bdt.prepare(jt)) {
+    return;
+  }
+  ResolvedMethodTableDeleteCheck stdc;
+  ResolvedMethodTableDoDelete stdd;
+  {
+    TraceTime timer("Clean", TRACETIME_LOG(Debug, membername, table, perf));
+    while(bdt.do_task(jt, stdc, stdd)) {
+      bdt.pause(jt);
+      {
+        ThreadBlockInVM tbivm(jt);
+      }
+      bdt.cont(jt);
+    }
+    bdt.done(jt);
+  }
+  log_info(membername, table)("Cleaned %ld of %ld", stdc._count, stdc._item);
 }
-#endif // PRODUCT
+void ResolvedMethodTable::reset_dead_counter() {
+  _uncleaned_items_count = 0;
+}
+
+void ResolvedMethodTable::inc_dead_counter(size_t ndead) {
+  size_t total = Atomic::add(ndead, &_uncleaned_items_count);
+  log_trace(membername, table)(
+     "Uncleaned items:" SIZE_FORMAT " added: " SIZE_FORMAT " total:" SIZE_FORMAT,
+     _uncleaned_items_count, ndead, total);
+}
+
+// After the parallel walk this method must be called to trigger
+// cleaning. Note it might trigger a resize instead.
+void ResolvedMethodTable::finish_dead_counter() {
+  check_concurrent_work();
+
+#ifdef ASSERT
+  if (SafepointSynchronize::is_at_safepoint()) {
+    size_t fail_cnt = verify_and_compare_entries();
+    if (fail_cnt != 0) {
+      tty->print_cr("ERROR: fail_cnt=" SIZE_FORMAT, fail_cnt);
+      guarantee(fail_cnt == 0, "unexpected ResolvedMethodTable verification failures");
+    }
+  }
+#endif // ASSERT
+}
 
 #if INCLUDE_JVMTI
+class AdjustMethodEntries : public StackObj {
+  bool* _trace_name_printed;
+public:
+  AdjustMethodEntries(bool* trace_name_printed) : _trace_name_printed(trace_name_printed) {};
+  bool operator()(WeakHandle<vm_resolved_method_table_data>* entry) {
+    oop mem_name = entry->peek();
+    if (mem_name == NULL) {
+      // Removed
+      return true;
+    }
+
+    Method* old_method = (Method*)java_lang_invoke_ResolvedMethodName::vmtarget(mem_name);
+
+    if (old_method->is_old()) {
+
+      Method* new_method = (old_method->is_deleted()) ?
+                            Universe::throw_no_such_method_error() :
+                            old_method->get_new_method();
+      java_lang_invoke_ResolvedMethodName::set_vmtarget(mem_name, new_method);
+
+      ResourceMark rm;
+      if (!(*_trace_name_printed)) {
+        log_info(redefine, class, update)("adjust: name=%s", old_method->method_holder()->external_name());
+         *_trace_name_printed = true;
+      }
+      log_debug(redefine, class, update, constantpool)
+        ("ResolvedMethod method update: %s(%s)",
+         new_method->name()->as_C_string(), new_method->signature()->as_C_string());
+    }
+
+    return true;
+  }
+};
+
 // It is called at safepoint only for RedefineClasses
 void ResolvedMethodTable::adjust_method_entries(bool * trace_name_printed) {
   assert(SafepointSynchronize::is_at_safepoint(), "only called at safepoint");
   // For each entry in RMT, change to new method
-  for (int i = 0; i < _the_table->table_size(); ++i) {
-    for (ResolvedMethodEntry* entry = _the_table->bucket(i);
-         entry != NULL;
-         entry = entry->next()) {
+  AdjustMethodEntries adjust(trace_name_printed);
+  _local_table->do_safepoint_scan(adjust);
+}
+#endif // INCLUDE_JVMTI
 
-      oop mem_name = entry->object_no_keepalive();
-      // except ones removed
-      if (mem_name == NULL) {
-        continue;
-      }
-      Method* old_method = (Method*)java_lang_invoke_ResolvedMethodName::vmtarget(mem_name);
-
-      if (old_method->is_old()) {
-
-        Method* new_method = (old_method->is_deleted()) ?
-                              Universe::throw_no_such_method_error() :
-                              old_method->get_new_method();
-        java_lang_invoke_ResolvedMethodName::set_vmtarget(mem_name, new_method);
-
-        ResourceMark rm;
-        if (!(*trace_name_printed)) {
-          log_info(redefine, class, update)("adjust: name=%s", old_method->method_holder()->external_name());
-           *trace_name_printed = true;
-        }
-        log_debug(redefine, class, update, constantpool)
-          ("ResolvedMethod method update: %s(%s)",
-           new_method->name()->as_C_string(), new_method->signature()->as_C_string());
+// Verification and comp
+class VerifyCompResolvedMethod : StackObj {
+  GrowableArray<oop>* _oops;
+ public:
+  size_t _errors;
+  VerifyCompResolvedMethod(GrowableArray<oop>* oops) : _oops(oops), _errors(0) {}
+  bool operator()(WeakHandle<vm_resolved_method_table_data>* val) {
+    oop s = val->peek();
+    if (s == NULL) {
+      return true;
+    }
+    int len = _oops->length();
+    for (int i = 0; i < len; i++) {
+      bool eq = s == _oops->at(i);
+      assert(!eq, "Duplicate entries");
+      if (eq) {
+        _errors++;
       }
     }
+    _oops->push(s);
+    return true;
+  };
+};
+
+size_t ResolvedMethodTable::items_count() {
+  return _items_count;
+}
+
+size_t ResolvedMethodTable::verify_and_compare_entries() {
+  Thread* thr = Thread::current();
+  GrowableArray<oop>* oops =
+    new (ResourceObj::C_HEAP, mtInternal)
+      GrowableArray<oop>((int)_current_size, true);
+
+  VerifyCompResolvedMethod vcs(oops);
+  if (!_local_table->try_scan(thr, vcs)) {
+    log_info(membername, table)("verify unavailable at this moment");
   }
+  delete oops;
+  return vcs._errors;
 }
-#endif // INCLUDE_JVMTI
--- a/src/hotspot/share/prims/resolvedMethodTable.hpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/prims/resolvedMethodTable.hpp	Wed Apr 10 15:41:04 2019 +0200
@@ -25,89 +25,78 @@
 #ifndef SHARE_PRIMS_RESOLVEDMETHODTABLE_HPP
 #define SHARE_PRIMS_RESOLVEDMETHODTABLE_HPP
 
+#include "gc/shared/oopStorage.hpp"
+#include "gc/shared/oopStorageParState.hpp"
+#include "memory/allocation.hpp"
 #include "oops/symbol.hpp"
 #include "oops/weakHandle.hpp"
+#include "utilities/concurrentHashTable.hpp"
 #include "utilities/hashtable.hpp"
 
-// Hashtable to record Method* used in ResolvedMethods, via. ResolvedMethod oops.
-// This is needed for redefinition to replace Method* with redefined versions.
+class ResolvedMethodTable;
+class ResolvedMethodTableConfig;
+typedef ConcurrentHashTable<WeakHandle<vm_resolved_method_table_data>, ResolvedMethodTableConfig, mtClass> ResolvedMethodTableHash;
 
-// Entry in a ResolvedMethodTable, mapping a ClassLoaderWeakHandle for a single oop of
-// java_lang_invoke_ResolvedMethodName which holds JVM Method* in vmtarget.
+class ResolvedMethodTable : public AllStatic {
+  static ResolvedMethodTableHash* _local_table;
+  static size_t                   _current_size;
 
-class ResolvedMethodEntry : public HashtableEntry<ClassLoaderWeakHandle, mtClass> {
- public:
-  ResolvedMethodEntry* next() const {
-    return (ResolvedMethodEntry*)HashtableEntry<ClassLoaderWeakHandle, mtClass>::next();
-  }
+  static OopStorage*              _weak_handles;
 
-  ResolvedMethodEntry** next_addr() {
-    return (ResolvedMethodEntry**)HashtableEntry<ClassLoaderWeakHandle, mtClass>::next_addr();
-  }
+  static volatile bool            _has_work;
 
-  oop object();
-  oop object_no_keepalive();
-
-  void print_on(outputStream* st) const;
-};
-
-class ResolvedMethodTable : public Hashtable<ClassLoaderWeakHandle, mtClass> {
-  enum Constants {
-    _table_size  = 1007
-  };
-
-  static int _total_oops_removed;
-
-  static bool _dead_entries;
-
-  static ResolvedMethodTable* _the_table;
-private:
-  ResolvedMethodEntry* bucket(int i) {
-    return (ResolvedMethodEntry*) Hashtable<ClassLoaderWeakHandle, mtClass>::bucket(i);
-  }
-
-  ResolvedMethodEntry** bucket_addr(int i) {
-    return (ResolvedMethodEntry**) Hashtable<ClassLoaderWeakHandle, mtClass>::bucket_addr(i);
-  }
-
-  unsigned int compute_hash(Method* method);
-
-  // need not be locked; no state change
-  oop lookup(int index, unsigned int hash, Method* method);
-  oop lookup(Method* method);
-
-  // must be done under ResolvedMethodTable_lock
-  oop basic_add(Method* method, Handle rmethod_name);
+  static volatile size_t          _items_count;
+  static volatile size_t          _uncleaned_items_count;
 
 public:
-  ResolvedMethodTable();
+  // Initialization
+  static void create_table();
 
-  static void create_table() {
-    assert(_the_table == NULL, "One symbol table allowed.");
-    _the_table = new ResolvedMethodTable();
-  }
+  static size_t table_size();
 
-  // Called from java_lang_invoke_ResolvedMethodName
-  static oop find_method(Method* method);
-  static oop add_method(const methodHandle& method, Handle rmethod_name);
+  // Lookup and inserts
+  static oop find_method(const Method* method);
+  static oop add_method(const Method* method, Handle rmethod_name);
 
-  static bool has_work() { return _dead_entries; }
-  static void trigger_cleanup();
+  // Callbacks
+  static void item_added();
+  static void item_removed();
 
-  static int removed_entries_count() { return _total_oops_removed; };
+  // Cleaning
+  static bool has_work();
 
-#if INCLUDE_JVMTI
-  // It is called at safepoint only for RedefineClasses
-  static void adjust_method_entries(bool * trace_name_printed);
-#endif // INCLUDE_JVMTI
+  // GC Support - Backing storage for the oop*s
+  static OopStorage* weak_storage();
 
-  // Cleanup cleared entries
-  static void unlink();
+  // Cleaning and table management
 
-#ifndef PRODUCT
-  void print();
-#endif
-  void verify();
+  static double get_load_factor();
+  static double get_dead_factor();
+
+  static void check_concurrent_work();
+  static void trigger_concurrent_work();
+  static void do_concurrent_work(JavaThread* jt);
+
+  static void grow(JavaThread* jt);
+  static void clean_dead_entries(JavaThread* jt);
+
+  // GC Notification
+
+  // Must be called before a parallel walk where objects might die.
+  static void reset_dead_counter();
+  // After the parallel walk this method must be called to trigger
+  // cleaning. Note it might trigger a resize instead.
+  static void finish_dead_counter();
+  // If GC uses ParState directly it should add the number of cleared
+  // entries to this method.
+  static void inc_dead_counter(size_t ndead);
+
+  // JVMTI Support - It is called at safepoint only for RedefineClasses
+  JVMTI_ONLY(static void adjust_method_entries(bool * trace_name_printed);)
+
+  // Debugging
+  static size_t items_count();
+  static size_t verify_and_compare_entries();
 };
 
 #endif // SHARE_PRIMS_RESOLVEDMETHODTABLE_HPP
--- a/src/hotspot/share/prims/whitebox.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/prims/whitebox.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -2092,8 +2092,8 @@
 #endif
 WB_END
 
-WB_ENTRY(jint, WB_ResolvedMethodRemovedCount(JNIEnv* env, jobject o))
-  return (jint) ResolvedMethodTable::removed_entries_count();
+WB_ENTRY(jlong, WB_ResolvedMethodItemsCount(JNIEnv* env, jobject o))
+  return (jlong) ResolvedMethodTable::items_count();
 WB_END
 
 WB_ENTRY(jint, WB_ProtectionDomainRemovedCount(JNIEnv* env, jobject o))
@@ -2337,7 +2337,7 @@
   {CC"isContainerized",           CC"()Z",            (void*)&WB_IsContainerized },
   {CC"printOsInfo",               CC"()V",            (void*)&WB_PrintOsInfo },
   {CC"disableElfSectionCache",    CC"()V",            (void*)&WB_DisableElfSectionCache },
-  {CC"resolvedMethodRemovedCount",     CC"()I",       (void*)&WB_ResolvedMethodRemovedCount },
+  {CC"resolvedMethodItemsCount",  CC"()J",            (void*)&WB_ResolvedMethodItemsCount },
   {CC"protectionDomainRemovedCount",   CC"()I",       (void*)&WB_ProtectionDomainRemovedCount },
   {CC"aotLibrariesCount", CC"()I",                    (void*)&WB_AotLibrariesCount },
 };
--- a/src/hotspot/share/runtime/mutexLocker.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/runtime/mutexLocker.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -54,7 +54,8 @@
 Mutex*   JNIHandleBlockFreeList_lock  = NULL;
 Mutex*   VMWeakAlloc_lock             = NULL;
 Mutex*   VMWeakActive_lock            = NULL;
-Mutex*   ResolvedMethodTable_lock     = NULL;
+Mutex*   ResolvedMethodTableWeakAlloc_lock  = NULL;
+Mutex*   ResolvedMethodTableWeakActive_lock = NULL;
 Mutex*   JmethodIdCreation_lock       = NULL;
 Mutex*   JfieldIdCreation_lock        = NULL;
 Monitor* JNICritical_lock             = NULL;
@@ -212,6 +213,9 @@
   def(StringTableWeakAlloc_lock    , PaddedMutex  , vmweak,      true,  Monitor::_safepoint_check_never);
   def(StringTableWeakActive_lock   , PaddedMutex  , vmweak-1,    true,  Monitor::_safepoint_check_never);
 
+  def(ResolvedMethodTableWeakAlloc_lock    , PaddedMutex  , vmweak,      true,  Monitor::_safepoint_check_never);
+  def(ResolvedMethodTableWeakActive_lock   , PaddedMutex  , vmweak-1,    true,  Monitor::_safepoint_check_never);
+
   if (UseConcMarkSweepGC || UseG1GC) {
     def(FullGCCount_lock           , PaddedMonitor, leaf,        true,  Monitor::_safepoint_check_never);      // in support of ExplicitGCInvokesConcurrent
   }
@@ -298,7 +302,6 @@
 
   def(Heap_lock                    , PaddedMonitor, nonleaf+1,   false, Monitor::_safepoint_check_sometimes);
   def(JfieldIdCreation_lock        , PaddedMutex  , nonleaf+1,   true,  Monitor::_safepoint_check_always);     // jfieldID, Used in VM_Operation
-  def(ResolvedMethodTable_lock     , PaddedMutex  , nonleaf+1,   false, Monitor::_safepoint_check_always);     // Used to protect ResolvedMethodTable
 
   def(CompiledIC_lock              , PaddedMutex  , nonleaf+2,   false, Monitor::_safepoint_check_never);      // locks VtableStubs_lock, InlineCacheBuffer_lock
   def(CompileTaskAlloc_lock        , PaddedMutex  , nonleaf+2,   true,  Monitor::_safepoint_check_always);
--- a/src/hotspot/share/runtime/mutexLocker.hpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/runtime/mutexLocker.hpp	Wed Apr 10 15:41:04 2019 +0200
@@ -48,7 +48,8 @@
 extern Mutex*   JNIHandleBlockFreeList_lock;     // a lock on the JNI handle block free list
 extern Mutex*   VMWeakAlloc_lock;                // VM Weak Handles storage allocate list lock
 extern Mutex*   VMWeakActive_lock;               // VM Weak Handles storage active list lock
-extern Mutex*   ResolvedMethodTable_lock;        // a lock on the ResolvedMethodTable updates
+extern Mutex*   ResolvedMethodTableWeakAlloc_lock;  // ResolvedMethodTable weak storage allocate list
+extern Mutex*   ResolvedMethodTableWeakActive_lock; // ResolvedMethodTable weak storage active list
 extern Mutex*   JmethodIdCreation_lock;          // a lock on creating JNI method identifiers
 extern Mutex*   JfieldIdCreation_lock;           // a lock on creating JNI static field identifiers
 extern Monitor* JNICritical_lock;                // a lock used while entering and exiting JNI critical regions, allows GC to sometimes get in
--- a/src/hotspot/share/runtime/serviceThread.cpp	Wed Apr 10 15:41:03 2019 +0200
+++ b/src/hotspot/share/runtime/serviceThread.cpp	Wed Apr 10 15:41:04 2019 +0200
@@ -190,7 +190,7 @@
     }
 
     if (resolved_method_table_work) {
-      ResolvedMethodTable::unlink();
+      ResolvedMethodTable::do_concurrent_work(jt);
     }
 
     if (protection_domain_table_work) {
--- a/test/hotspot/jtreg/runtime/MemberName/MemberNameLeak.java	Wed Apr 10 15:41:03 2019 +0200
+++ b/test/hotspot/jtreg/runtime/MemberName/MemberNameLeak.java	Wed Apr 10 15:41:04 2019 +0200
@@ -25,14 +25,16 @@
  * @test
  * @bug 8174749 8213307
  * @summary MemberNameTable should reuse entries
- * @requires vm.gc == "null"
- * @library /test/lib
+ * @library /test/lib /runtime/testlibrary
+ * @modules java.base/jdk.internal.misc
+ * @modules java.compiler
  * @build sun.hotspot.WhiteBox
  * @run driver ClassFileInstaller sun.hotspot.WhiteBox sun.hotspot.WhiteBox$WhiteBoxPermission
  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. MemberNameLeak
  */
 
 import java.lang.invoke.*;
+import java.lang.reflect.*;
 import jdk.test.lib.process.OutputAnalyzer;
 import jdk.test.lib.process.ProcessTools;
 import sun.hotspot.WhiteBox;
@@ -40,6 +42,12 @@
 import sun.hotspot.gc.GC;
 
 public class MemberNameLeak {
+    private static String className  = "MemberNameLeakTestClass";
+    private static String methodPrefix = "method";
+    // The size of the ResolvedMethodTable is 1024. 2000 entries
+    // is enough to trigger a grow/cleaning of the table after a GC.
+    private static int methodCount = 2000;
+
     static class Leak {
       public void callMe() {
       }
@@ -47,15 +55,31 @@
       public static void main(String[] args) throws Throwable {
         Leak leak = new Leak();
         WhiteBox wb = WhiteBox.getWhiteBox();
-        int removedCountOrig =  wb.resolvedMethodRemovedCount();
-        int removedCount;
 
-        for (int i = 0; i < 10; i++) {
-          MethodHandles.Lookup lookup = MethodHandles.lookup();
-          MethodType mt = MethodType.fromMethodDescriptorString("()V", Leak.class.getClassLoader());
+        ClassWithManyMethodsClassLoader classLoader = new ClassWithManyMethodsClassLoader();
+        Class<?> clazz = classLoader.create(className, methodPrefix, methodCount);
+
+        long before = wb.resolvedMethodItemsCount();
+
+        Object o = clazz.newInstance();
+        MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup());
+
+        for (int i = 0; i < methodCount; i++) {
+          MethodType mt = MethodType.fromMethodDescriptorString("()V", classLoader);
+          String methodName = methodPrefix + i;
           // findSpecial leaks some native mem
-          MethodHandle mh = lookup.findSpecial(Leak.class, "callMe", mt, Leak.class);
-          mh.invokeExact(leak);
+          // Add entry to ResolvedMethodTable.
+          MethodHandle mh0 = lookup.findSpecial(clazz, methodName, mt, clazz);
+          // Find entry in ResolvedMethodTable.
+          MethodHandle mh1 = lookup.findSpecial(clazz, methodName, mt, clazz);
+
+          mh1.invoke(o);
+        }
+
+        long after = wb.resolvedMethodItemsCount();
+
+        if (after == before) {
+          throw new RuntimeException("Too few resolved methods");
         }
 
         // Wait until ServiceThread cleans ResolvedMethod table
@@ -64,16 +88,19 @@
           if (cnt++ % 30 == 0) {
             System.gc();  // make mh unused
           }
-          removedCount = wb.resolvedMethodRemovedCount();
-          if (removedCountOrig != removedCount) {
+
+          if (after != wb.resolvedMethodItemsCount()) {
+            // Entries have been removed.
             break;
           }
+
           Thread.sleep(100);
         }
       }
     }
 
-    public static void test(String gc, boolean doConcurrent) throws Throwable {
+    public static void test(GC gc, boolean doConcurrent) throws Throwable {
+        System.err.println("test(" + gc + ", " + doConcurrent + ")");
         // Run this Leak class with logging
         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
                                       "-Xlog:membername+table=trace",
@@ -84,27 +111,35 @@
                                       doConcurrent ? "-XX:+ExplicitGCInvokesConcurrent" : "-XX:-ExplicitGCInvokesConcurrent",
                                       "-XX:+ClassUnloading",
                                       "-XX:+ClassUnloadingWithConcurrentMark",
-                                      gc, Leak.class.getName());
+                                      "-XX:+Use" + gc + "GC",
+                                      Leak.class.getName());
         OutputAnalyzer output = new OutputAnalyzer(pb.start());
-        output.shouldContain("ResolvedMethod entry added for MemberNameLeak$Leak.callMe()V");
-        output.shouldContain("ResolvedMethod entry found for MemberNameLeak$Leak.callMe()V");
+        // Hardcoded names for classes generated by GeneratedClassLoader
+        String descriptor = className + "." + methodPrefix + "0()V";
+        output.shouldContain("ResolvedMethod entry added for " + descriptor);
+        output.shouldContain("ResolvedMethod entry found for " + descriptor);
         output.shouldContain("ResolvedMethod entry removed");
         output.shouldHaveExitValue(0);
     }
 
-    public static void main(java.lang.String[] unused) throws Throwable {
-        test("-XX:+UseG1GC", false);
-        test("-XX:+UseG1GC", true);
+    private static boolean supportsSTW(GC gc) {
+        return !(gc == GC.Epsilon);
+    }
 
-        test("-XX:+UseParallelGC", false);
-        test("-XX:+UseSerialGC", false);
-        if (!Compiler.isGraalEnabled()) { // Graal does not support CMS
-            test("-XX:+UseConcMarkSweepGC", false);
-            test("-XX:+UseConcMarkSweepGC", true);
-            if (GC.Shenandoah.isSupported()) {
-                test("-XX:+UseShenandoahGC", true);
-                test("-XX:+UseShenandoahGC", false);
-            }
+    private static boolean supportsConcurrent(GC gc) {
+        return !(gc == GC.Epsilon || gc == GC.Serial || gc == GC.Parallel);
+    }
+
+    private static void test(GC gc) throws Throwable {
+        if (supportsSTW(gc)) {
+            test(gc, false);
+        }
+        if (supportsConcurrent(gc)) {
+            test(gc, true);
         }
     }
+
+    public static void main(java.lang.String[] unused) throws Throwable {
+      test(GC.selected());
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/testlibrary/ClassWithManyMethodsClassLoader.java	Wed Apr 10 15:41:04 2019 +0200
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.DataInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.tools.JavaCompiler;
+import javax.tools.ToolProvider;
+
+/**
+ * A factory that generates a class with many methods.
+ */
+public class ClassWithManyMethodsClassLoader extends ClassLoader {
+    /**
+     * Used to enable/disable keeping the class files and java sources for
+     * the generated classes.
+     */
+    private static boolean deleteFiles = Boolean.parseBoolean(
+        System.getProperty("ClassWithManyMethodsClassLoader.deleteFiles", "true"));
+
+    private JavaCompiler javac;
+
+    public ClassWithManyMethodsClassLoader() {
+        javac = ToolProvider.getSystemJavaCompiler();
+    }
+
+    private String generateSource(String className, String methodPrefix, int methodCount) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("public class ")
+            .append(className)
+            .append("{\n");
+
+        for (int i = 0; i < methodCount; i++) {
+            sb.append("public void ")
+                .append(methodPrefix)
+                .append(i)
+                .append("() {}\n");
+        }
+
+        sb.append("\n}");
+
+        return sb.toString();
+    }
+
+    private byte[] generateClassBytes(String className, String methodPrefix, int methodCount) throws IOException {
+        String src = generateSource(className, methodPrefix, methodCount);
+        File file = new File(className + ".java");
+        try (PrintWriter pw = new PrintWriter(new FileWriter(file))) {
+            pw.append(src);
+            pw.flush();
+        }
+        ByteArrayOutputStream err = new ByteArrayOutputStream();
+        int exitcode = javac.run(null, null, err, file.getCanonicalPath());
+        if (exitcode != 0) {
+            // Print Error
+            System.err.print(err);
+            if (err.toString().contains("java.lang.OutOfMemoryError: Java heap space")) {
+                throw new OutOfMemoryError("javac failed with resources exhausted");
+            } else {
+              throw new RuntimeException("javac failure when compiling: " +
+                      file.getCanonicalPath());
+            }
+        } else {
+            if (deleteFiles) {
+                file.delete();
+            }
+        }
+
+        File classFile = new File(className + ".class");
+        byte[] bytes;
+        try (DataInputStream dis = new DataInputStream(new FileInputStream(classFile))) {
+            bytes = new byte[dis.available()];
+            dis.readFully(bytes);
+        }
+        if (deleteFiles) {
+            classFile.delete();
+        }
+
+        return bytes;
+    }
+
+    public Class<?> create(String className, String methodPrefix, int methodCount) throws IOException {
+        byte[] bytes = generateClassBytes(className, methodPrefix, methodCount);
+        return defineClass(className, bytes, 0, bytes.length);
+    }
+}
--- a/test/lib/sun/hotspot/WhiteBox.java	Wed Apr 10 15:41:03 2019 +0200
+++ b/test/lib/sun/hotspot/WhiteBox.java	Wed Apr 10 15:41:04 2019 +0200
@@ -541,7 +541,7 @@
   public native void disableElfSectionCache();
 
   // Resolved Method Table
-  public native int resolvedMethodRemovedCount();
+  public native long resolvedMethodItemsCount();
 
   // Protection Domain Table
   public native int protectionDomainRemovedCount();