8221393: ResolvedMethodTable too small for StackWalking applications
Reviewed-by: coleenp, rehn
--- 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;
+ }
+
+ InstanceKlass* k = SystemDictionary::ResolvedMethodName_klass();
+ if (!k->is_initialized()) {
+ k->initialize(CHECK_NULL);
}
- return resolved_method;
+
+ 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"
-
-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;
+}
--- 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.
-
-// Entry in a ResolvedMethodTable, mapping a ClassLoaderWeakHandle for a single oop of
-// java_lang_invoke_ResolvedMethodName which holds JVM Method* in vmtarget.
-
-class ResolvedMethodEntry : public HashtableEntry<ClassLoaderWeakHandle, mtClass> {
- public:
- ResolvedMethodEntry* next() const {
- return (ResolvedMethodEntry*)HashtableEntry<ClassLoaderWeakHandle, mtClass>::next();
- }
-
- ResolvedMethodEntry** next_addr() {
- return (ResolvedMethodEntry**)HashtableEntry<ClassLoaderWeakHandle, mtClass>::next_addr();
- }
-
- oop object();
- oop object_no_keepalive();
-
- void print_on(outputStream* st) const;
-};
+class ResolvedMethodTable;
+class ResolvedMethodTableConfig;
+typedef ConcurrentHashTable<WeakHandle<vm_resolved_method_table_data>, ResolvedMethodTableConfig, mtClass> ResolvedMethodTableHash;
-class ResolvedMethodTable : public Hashtable<ClassLoaderWeakHandle, mtClass> {
- enum Constants {
- _table_size = 1007
- };
-
- static int _total_oops_removed;
-
- static bool _dead_entries;
+class ResolvedMethodTable : public AllStatic {
+ static ResolvedMethodTableHash* _local_table;
+ static size_t _current_size;
- static ResolvedMethodTable* _the_table;
-private:
- ResolvedMethodEntry* bucket(int i) {
- return (ResolvedMethodEntry*) Hashtable<ClassLoaderWeakHandle, mtClass>::bucket(i);
- }
+ static OopStorage* _weak_handles;
- ResolvedMethodEntry** bucket_addr(int i) {
- return (ResolvedMethodEntry**) Hashtable<ClassLoaderWeakHandle, mtClass>::bucket_addr(i);
- }
-
- unsigned int compute_hash(Method* method);
+ static volatile bool _has_work;
- // 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();
+
+ // Lookup and inserts
+ static oop find_method(const Method* method);
+ static oop add_method(const Method* method, Handle rmethod_name);
- // Called from java_lang_invoke_ResolvedMethodName
- static oop find_method(Method* method);
- static oop add_method(const methodHandle& method, Handle rmethod_name);
+ // Callbacks
+ static void item_added();
+ static void item_removed();
+
+ // Cleaning
+ static bool has_work();
- static bool has_work() { return _dead_entries; }
- static void trigger_cleanup();
+ // GC Support - Backing storage for the oop*s
+ static OopStorage* weak_storage();
+
+ // Cleaning and table management
+
+ static double get_load_factor();
+ static double get_dead_factor();
- static int removed_entries_count() { return _total_oops_removed; };
+ static void check_concurrent_work();
+ static void trigger_concurrent_work();
+ static void do_concurrent_work(JavaThread* jt);
-#if INCLUDE_JVMTI
- // It is called at safepoint only for RedefineClasses
- static void adjust_method_entries(bool * trace_name_printed);
-#endif // INCLUDE_JVMTI
+ static void grow(JavaThread* jt);
+ static void clean_dead_entries(JavaThread* jt);
+
+ // GC Notification
- // Cleanup cleared entries
- static void unlink();
+ // 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);
-#ifndef PRODUCT
- void print();
-#endif
- void verify();
+ // 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;
+
+ 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 < 10; i++) {
- MethodHandles.Lookup lookup = MethodHandles.lookup();
- MethodType mt = MethodType.fromMethodDescriptorString("()V", Leak.class.getClassLoader());
+ 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();