--- 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;
+}