src/hotspot/share/prims/resolvedMethodTable.cpp
changeset 54511 fbfcebad8e66
parent 54431 ad9fa99fa48e
child 54574 7b74bbe5085b
--- 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"
 
-
-oop ResolvedMethodEntry::object() {
-  return literal().resolve();
-}
-
-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)) { }
+// 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 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();
+static const size_t ResolvedMethodTableSizeLog = 10;
 
-      // 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);
+}
+
+size_t ResolvedMethodTable::table_size() {
+  return (size_t)1 << _local_table->get_size_log2(Thread::current());
 }
 
-oop ResolvedMethodTable::basic_add(Method* method, Handle rmethod_name) {
-  assert_locked_or_safepoint(ResolvedMethodTable_lock);
+class ResolvedMethodTableLookup : StackObj {
+ private:
+  Thread*       _thread;
+  uintx         _hash;
+  const Method* _method;
+  Handle        _found;
+
+ 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;
+  }
+};
+
 
-  unsigned int hash = compute_hash(method);
-  int index = hash_to_index(hash);
+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();
 
-  // One was added while aquiring the lock
-  oop entry = lookup(index, hash, method);
-  if (entry != NULL) {
-    return entry;
+  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;
+void ResolvedMethodTable::item_added() {
+  Atomic::inc(&_items_count);
+}
+
+void ResolvedMethodTable::item_removed() {
+  Atomic::dec(&_items_count);
+  log_trace(membername, table) ("ResolvedMethod entry removed");
+}
 
-oop ResolvedMethodTable::find_method(Method* method) {
-  MutexLocker ml(ResolvedMethodTable_lock);
-  oop entry = _the_table->lookup(method);
-  return entry;
+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;
 }
 
-oop ResolvedMethodTable::add_method(const methodHandle& m, Handle resolved_method_name) {
-  MutexLocker ml(ResolvedMethodTable_lock);
-  DEBUG_ONLY(NoSafepointVerifier nsv);
+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;
 
-  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);
+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);
+    }
+  }
+  gt.done(jt);
+  _current_size = table_size();
+  log_info(membername, table)("Grown to size:" SIZE_FORMAT, _current_size);
+}
+
+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;
     }
   }
-  log_debug(membername, table) ("ResolvedMethod entries counted %d removed %d",
-                                _oops_counted, _oops_removed);
-  _total_oops_removed += _oops_removed;
-  _dead_entries = 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);
+}
+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);
 }
 
-#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();
+// 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
 }
-#endif // PRODUCT
 
 #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()) {
-
-      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()) {
+  AdjustMethodEntries adjust(trace_name_printed);
+  _local_table->do_safepoint_scan(adjust);
+}
+#endif // INCLUDE_JVMTI
 
-        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;
 }
-#endif // INCLUDE_JVMTI
+
+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;
+}