8221393: ResolvedMethodTable too small for StackWalking applications
Reviewed-by: coleenp, rehn
This commit is contained in:
parent
52c427ae20
commit
23f02171c8
src/hotspot/share
classfile
gc
shared
z
oops
prims
runtime
test
hotspot/jtreg/runtime
lib/sun/hotspot
@ -3635,23 +3635,48 @@ void java_lang_invoke_ResolvedMethodName::set_vmtarget(oop resolved_method, Meth
|
||||
resolved_method->address_field_put(_vmtarget_offset, (address)m);
|
||||
}
|
||||
|
||||
void java_lang_invoke_ResolvedMethodName::set_vmholder(oop resolved_method, oop holder) {
|
||||
assert(is_instance(resolved_method), "wrong type");
|
||||
resolved_method->obj_field_put(_vmholder_offset, holder);
|
||||
}
|
||||
|
||||
oop java_lang_invoke_ResolvedMethodName::find_resolved_method(const methodHandle& m, TRAPS) {
|
||||
const Method* method = m();
|
||||
|
||||
// lookup ResolvedMethod oop in the table, or create a new one and intern it
|
||||
oop resolved_method = ResolvedMethodTable::find_method(m());
|
||||
if (resolved_method == NULL) {
|
||||
InstanceKlass* k = SystemDictionary::ResolvedMethodName_klass();
|
||||
if (!k->is_initialized()) {
|
||||
k->initialize(CHECK_NULL);
|
||||
}
|
||||
oop new_resolved_method = k->allocate_instance(CHECK_NULL);
|
||||
new_resolved_method->address_field_put(_vmtarget_offset, (address)m());
|
||||
// Add a reference to the loader (actually mirror because unsafe anonymous classes will not have
|
||||
// distinct loaders) to ensure the metadata is kept alive.
|
||||
// This mirror may be different than the one in clazz field.
|
||||
new_resolved_method->obj_field_put(_vmholder_offset, m->method_holder()->java_mirror());
|
||||
resolved_method = ResolvedMethodTable::add_method(m, Handle(THREAD, new_resolved_method));
|
||||
oop resolved_method = ResolvedMethodTable::find_method(method);
|
||||
if (resolved_method != NULL) {
|
||||
return resolved_method;
|
||||
}
|
||||
return resolved_method;
|
||||
|
||||
InstanceKlass* k = SystemDictionary::ResolvedMethodName_klass();
|
||||
if (!k->is_initialized()) {
|
||||
k->initialize(CHECK_NULL);
|
||||
}
|
||||
|
||||
oop new_resolved_method = k->allocate_instance(CHECK_NULL);
|
||||
|
||||
NoSafepointVerifier nsv;
|
||||
|
||||
if (method->is_old()) {
|
||||
method = (method->is_deleted()) ? Universe::throw_no_such_method_error() :
|
||||
method->get_new_method();
|
||||
}
|
||||
|
||||
InstanceKlass* holder = method->method_holder();
|
||||
|
||||
set_vmtarget(new_resolved_method, const_cast<Method*>(method));
|
||||
// Add a reference to the loader (actually mirror because unsafe anonymous classes will not have
|
||||
// distinct loaders) to ensure the metadata is kept alive.
|
||||
// This mirror may be different than the one in clazz field.
|
||||
set_vmholder(new_resolved_method, holder->java_mirror());
|
||||
|
||||
// Set flag in class to indicate this InstanceKlass has entries in the table
|
||||
// to avoid walking table during redefinition if none of the redefined classes
|
||||
// have any membernames in the table.
|
||||
holder->set_has_resolved_methods();
|
||||
|
||||
return ResolvedMethodTable::add_method(method, Handle(THREAD, new_resolved_method));
|
||||
}
|
||||
|
||||
oop java_lang_invoke_LambdaForm::vmentry(oop lform) {
|
||||
|
@ -1063,6 +1063,8 @@ class java_lang_invoke_ResolvedMethodName : AllStatic {
|
||||
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);
|
||||
|
||||
|
@ -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 @@ bool SystemDictionary::do_unloading(GCTimer* gc_timer) {
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -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 @@ void WeakProcessor::Task::initialize() {
|
||||
new (states++) StorageState(storage, _nworkers);
|
||||
}
|
||||
StringTable::reset_dead_counter();
|
||||
ResolvedMethodTable::reset_dead_counter();
|
||||
}
|
||||
|
||||
WeakProcessor::Task::Task(uint nworkers) :
|
||||
@ -134,6 +146,7 @@ WeakProcessor::Task::~Task() {
|
||||
FREE_C_HEAP_ARRAY(StorageState, _storage_states);
|
||||
}
|
||||
StringTable::finish_dead_counter();
|
||||
ResolvedMethodTable::finish_dead_counter();
|
||||
}
|
||||
|
||||
void WeakProcessor::GangTask::work(uint worker_id) {
|
||||
|
@ -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 @@ void WeakProcessor::Task::work(uint worker_id,
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 @@ const char* WeakProcessorPhases::description(Phase phase) {
|
||||
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 @@ OopStorage* WeakProcessorPhases::oop_storage(Phase phase) {
|
||||
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 @@ OopStorage* WeakProcessorPhases::oop_storage(Phase phase) {
|
||||
bool WeakProcessorPhases::is_stringtable(Phase phase) {
|
||||
return phase == stringtable;
|
||||
}
|
||||
|
||||
bool WeakProcessorPhases::is_resolved_method_table(Phase phase) {
|
||||
return phase == resolved_method_table;
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ public:
|
||||
// OopStorage phases.
|
||||
jni,
|
||||
stringtable,
|
||||
resolved_method_table,
|
||||
vm
|
||||
};
|
||||
|
||||
@ -68,6 +69,7 @@ public:
|
||||
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;
|
||||
|
@ -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 ZSubPhaseConcurrentWeakRoots("Concurrent Weak Roots")
|
||||
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 @@ ZConcurrentWeakRootsIterator::ZConcurrentWeakRootsIterator() :
|
||||
_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 @@ void ZConcurrentWeakRootsIterator::do_jni_weak_handles(ZRootsIteratorClosure* cl
|
||||
_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 @@ public:
|
||||
|
||||
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() :
|
||||
|
@ -149,14 +149,17 @@ private:
|
||||
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();
|
||||
|
@ -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 @@ template <> OopStorage* WeakHandle<vm_string_table_data>::get_storage() {
|
||||
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 @@ void WeakHandle<T>::print_on(outputStream* st) const {
|
||||
// Provide instantiation.
|
||||
template class WeakHandle<vm_class_loader_data>;
|
||||
template class WeakHandle<vm_string_table_data>;
|
||||
|
||||
template class WeakHandle<vm_resolved_method_table_data>;
|
||||
|
@ -39,7 +39,7 @@ class OopStorage;
|
||||
// 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 @@ class WeakHandle {
|
||||
void print_on(outputStream* st) const;
|
||||
};
|
||||
|
||||
typedef WeakHandle<vm_class_loader_data> ClassLoaderWeakHandle;
|
||||
|
||||
#endif // SHARE_OOPS_WEAKHANDLE_HPP
|
||||
|
@ -24,223 +24,422 @@
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "classfile/javaClasses.hpp"
|
||||
#include "gc/shared/oopStorage.inline.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "memory/resourceArea.hpp"
|
||||
#include "oops/access.inline.hpp"
|
||||
#include "oops/oop.inline.hpp"
|
||||
#include "oops/method.hpp"
|
||||
#include "oops/symbol.hpp"
|
||||
#include "oops/weakHandle.inline.hpp"
|
||||
#include "prims/resolvedMethodTable.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
#include "runtime/interfaceSupport.inline.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
#include "runtime/safepointVerifiers.hpp"
|
||||
#include "utilities/hashtable.inline.hpp"
|
||||
#include "runtime/timerTrace.hpp"
|
||||
#include "utilities/concurrentHashTable.inline.hpp"
|
||||
#include "utilities/concurrentHashTableTasks.inline.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
|
||||
// 2^24 is max size
|
||||
static const size_t END_SIZE = 24;
|
||||
// If a chain gets to 32 something might be wrong
|
||||
static const size_t GROW_HINT = 32;
|
||||
|
||||
oop ResolvedMethodEntry::object() {
|
||||
return literal().resolve();
|
||||
}
|
||||
static const size_t ResolvedMethodTableSizeLog = 10;
|
||||
|
||||
oop ResolvedMethodEntry::object_no_keepalive() {
|
||||
// The AS_NO_KEEPALIVE peeks at the oop without keeping it alive.
|
||||
// This is dangerous in general but is okay if the loaded oop does
|
||||
// not leak out past a thread transition where a safepoint can happen.
|
||||
// A subsequent oop_load without AS_NO_KEEPALIVE (the object() accessor)
|
||||
// keeps the oop alive before doing so.
|
||||
return literal().peek();
|
||||
}
|
||||
|
||||
ResolvedMethodTable::ResolvedMethodTable()
|
||||
: Hashtable<ClassLoaderWeakHandle, mtClass>(_table_size, sizeof(ResolvedMethodEntry)) { }
|
||||
|
||||
oop ResolvedMethodTable::lookup(int index, unsigned int hash, Method* method) {
|
||||
assert_locked_or_safepoint(ResolvedMethodTable_lock);
|
||||
for (ResolvedMethodEntry* p = bucket(index); p != NULL; p = p->next()) {
|
||||
if (p->hash() == hash) {
|
||||
|
||||
// Peek the object to check if it is the right target.
|
||||
oop target = p->object_no_keepalive();
|
||||
|
||||
// The method is in the table as a target already
|
||||
if (target != NULL && java_lang_invoke_ResolvedMethodName::vmtarget(target) == method) {
|
||||
ResourceMark rm;
|
||||
log_debug(membername, table) ("ResolvedMethod entry found for %s index %d",
|
||||
method->name_and_sig_as_C_string(), index);
|
||||
// The object() accessor makes sure the target object is kept alive before
|
||||
// leaking out.
|
||||
return p->object();
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned int ResolvedMethodTable::compute_hash(Method* method) {
|
||||
unsigned int method_hash(const Method* method) {
|
||||
unsigned int name_hash = method->name()->identity_hash();
|
||||
unsigned int signature_hash = method->signature()->identity_hash();
|
||||
return name_hash ^ signature_hash;
|
||||
}
|
||||
|
||||
|
||||
oop ResolvedMethodTable::lookup(Method* method) {
|
||||
unsigned int hash = compute_hash(method);
|
||||
int index = hash_to_index(hash);
|
||||
return lookup(index, hash, method);
|
||||
}
|
||||
|
||||
oop ResolvedMethodTable::basic_add(Method* method, Handle rmethod_name) {
|
||||
assert_locked_or_safepoint(ResolvedMethodTable_lock);
|
||||
|
||||
unsigned int hash = compute_hash(method);
|
||||
int index = hash_to_index(hash);
|
||||
|
||||
// One was added while aquiring the lock
|
||||
oop entry = lookup(index, hash, method);
|
||||
if (entry != NULL) {
|
||||
return entry;
|
||||
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);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ResolvedMethodGet : public StackObj {
|
||||
Thread* _thread;
|
||||
const Method* _method;
|
||||
Handle _return;
|
||||
public:
|
||||
ResolvedMethodGet(Thread* thread, const Method* method) : _thread(thread), _method(method) {}
|
||||
void operator()(WeakHandle<vm_resolved_method_table_data>* val) {
|
||||
oop result = val->resolve();
|
||||
assert(result != NULL, "Result should be reachable");
|
||||
_return = Handle(_thread, result);
|
||||
log_get();
|
||||
}
|
||||
oop get_res_oop() {
|
||||
return _return();
|
||||
}
|
||||
void log_get() {
|
||||
LogTarget(Trace, membername, table) log;
|
||||
if (log.is_enabled()) {
|
||||
ResourceMark rm;
|
||||
log.print("ResolvedMethod entry found for %s",
|
||||
_method->name_and_sig_as_C_string());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
oop ResolvedMethodTable::find_method(const Method* method) {
|
||||
Thread* thread = Thread::current();
|
||||
|
||||
ResolvedMethodTableLookup lookup(thread, method_hash(method), method);
|
||||
ResolvedMethodGet rmg(thread, method);
|
||||
_local_table->get(thread, lookup, rmg);
|
||||
|
||||
return rmg.get_res_oop();
|
||||
}
|
||||
|
||||
static void log_insert(const Method* method) {
|
||||
LogTarget(Debug, membername, table) log;
|
||||
if (log.is_enabled()) {
|
||||
ResourceMark rm;
|
||||
log_debug(membername, table) ("ResolvedMethod entry added for %s",
|
||||
method->name_and_sig_as_C_string());
|
||||
}
|
||||
}
|
||||
|
||||
oop ResolvedMethodTable::add_method(const Method* method, Handle rmethod_name) {
|
||||
Thread* thread = Thread::current();
|
||||
|
||||
ResolvedMethodTableLookup lookup(thread, method_hash(method), method);
|
||||
ResolvedMethodGet rmg(thread, method);
|
||||
|
||||
while (true) {
|
||||
if (_local_table->get(thread, lookup, rmg)) {
|
||||
return rmg.get_res_oop();
|
||||
}
|
||||
WeakHandle<vm_resolved_method_table_data> wh = WeakHandle<vm_resolved_method_table_data>::create(rmethod_name);
|
||||
// The hash table takes ownership of the WeakHandle, even if it's not inserted.
|
||||
if (_local_table->insert(thread, lookup, wh)) {
|
||||
log_insert(method);
|
||||
return wh.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
ClassLoaderWeakHandle w = ClassLoaderWeakHandle::create(rmethod_name);
|
||||
ResolvedMethodEntry* p = (ResolvedMethodEntry*) Hashtable<ClassLoaderWeakHandle, mtClass>::new_entry(hash, w);
|
||||
Hashtable<ClassLoaderWeakHandle, mtClass>::add_entry(index, p);
|
||||
ResourceMark rm;
|
||||
log_debug(membername, table) ("ResolvedMethod entry added for %s index %d",
|
||||
method->name_and_sig_as_C_string(), index);
|
||||
return rmethod_name();
|
||||
}
|
||||
|
||||
ResolvedMethodTable* ResolvedMethodTable::_the_table = NULL;
|
||||
|
||||
oop ResolvedMethodTable::find_method(Method* method) {
|
||||
MutexLocker ml(ResolvedMethodTable_lock);
|
||||
oop entry = _the_table->lookup(method);
|
||||
return entry;
|
||||
void ResolvedMethodTable::item_added() {
|
||||
Atomic::inc(&_items_count);
|
||||
}
|
||||
|
||||
oop ResolvedMethodTable::add_method(const methodHandle& m, Handle resolved_method_name) {
|
||||
MutexLocker ml(ResolvedMethodTable_lock);
|
||||
DEBUG_ONLY(NoSafepointVerifier nsv);
|
||||
void ResolvedMethodTable::item_removed() {
|
||||
Atomic::dec(&_items_count);
|
||||
log_trace(membername, table) ("ResolvedMethod entry removed");
|
||||
}
|
||||
|
||||
Method* method = m();
|
||||
// Check if method has been redefined while taking out ResolvedMethodTable_lock, if so
|
||||
// use new method in the ResolvedMethodName. The old method won't be deallocated
|
||||
// yet because it's passed in as a Handle.
|
||||
if (method->is_old()) {
|
||||
method = (method->is_deleted()) ? Universe::throw_no_such_method_error() :
|
||||
method->get_new_method();
|
||||
java_lang_invoke_ResolvedMethodName::set_vmtarget(resolved_method_name(), method);
|
||||
bool ResolvedMethodTable::has_work() {
|
||||
return _has_work;
|
||||
}
|
||||
|
||||
OopStorage* ResolvedMethodTable::weak_storage() {
|
||||
return _weak_handles;
|
||||
}
|
||||
|
||||
double ResolvedMethodTable::get_load_factor() {
|
||||
return (double)_items_count/_current_size;
|
||||
}
|
||||
|
||||
double ResolvedMethodTable::get_dead_factor() {
|
||||
return (double)_uncleaned_items_count/_current_size;
|
||||
}
|
||||
|
||||
static const double PREF_AVG_LIST_LEN = 2.0;
|
||||
// If we have as many dead items as 50% of the number of bucket
|
||||
static const double CLEAN_DEAD_HIGH_WATER_MARK = 0.5;
|
||||
|
||||
void ResolvedMethodTable::check_concurrent_work() {
|
||||
if (_has_work) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set flag in class to indicate this InstanceKlass has entries in the table
|
||||
// to avoid walking table during redefinition if none of the redefined classes
|
||||
// have any membernames in the table.
|
||||
method->method_holder()->set_has_resolved_methods();
|
||||
|
||||
return _the_table->basic_add(method, resolved_method_name);
|
||||
double load_factor = get_load_factor();
|
||||
double dead_factor = get_dead_factor();
|
||||
// We should clean/resize if we have more dead than alive,
|
||||
// more items than preferred load factor or
|
||||
// more dead items than water mark.
|
||||
if ((dead_factor > load_factor) ||
|
||||
(load_factor > PREF_AVG_LIST_LEN) ||
|
||||
(dead_factor > CLEAN_DEAD_HIGH_WATER_MARK)) {
|
||||
log_debug(membername, table)("Concurrent work triggered, live factor: %g dead factor: %g",
|
||||
load_factor, dead_factor);
|
||||
trigger_concurrent_work();
|
||||
}
|
||||
}
|
||||
|
||||
// Removing entries
|
||||
int ResolvedMethodTable::_total_oops_removed = 0;
|
||||
|
||||
// There are no dead entries at start
|
||||
bool ResolvedMethodTable::_dead_entries = false;
|
||||
|
||||
void ResolvedMethodTable::trigger_cleanup() {
|
||||
void ResolvedMethodTable::trigger_concurrent_work() {
|
||||
MutexLockerEx ml(Service_lock, Mutex::_no_safepoint_check_flag);
|
||||
_dead_entries = true;
|
||||
_has_work = true;
|
||||
Service_lock->notify_all();
|
||||
}
|
||||
|
||||
// Serially invoke removed unused oops from the table.
|
||||
// This is done by the ServiceThread after being notified on class unloading
|
||||
void ResolvedMethodTable::unlink() {
|
||||
MutexLocker ml(ResolvedMethodTable_lock);
|
||||
int _oops_removed = 0;
|
||||
int _oops_counted = 0;
|
||||
for (int i = 0; i < _the_table->table_size(); ++i) {
|
||||
ResolvedMethodEntry** p = _the_table->bucket_addr(i);
|
||||
ResolvedMethodEntry* entry = _the_table->bucket(i);
|
||||
while (entry != NULL) {
|
||||
_oops_counted++;
|
||||
oop l = entry->object_no_keepalive();
|
||||
if (l != NULL) {
|
||||
p = entry->next_addr();
|
||||
} else {
|
||||
// Entry has been removed.
|
||||
_oops_removed++;
|
||||
if (log_is_enabled(Debug, membername, table)) {
|
||||
log_debug(membername, table) ("ResolvedMethod entry removed for index %d", i);
|
||||
}
|
||||
entry->literal().release();
|
||||
*p = entry->next();
|
||||
_the_table->free_entry(entry);
|
||||
}
|
||||
// get next entry
|
||||
entry = (ResolvedMethodEntry*)HashtableEntry<ClassLoaderWeakHandle, mtClass>::make_ptr(*p);
|
||||
}
|
||||
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);
|
||||
}
|
||||
log_debug(membername, table) ("ResolvedMethod entries counted %d removed %d",
|
||||
_oops_counted, _oops_removed);
|
||||
_total_oops_removed += _oops_removed;
|
||||
_dead_entries = false;
|
||||
}
|
||||
|
||||
#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();
|
||||
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);
|
||||
}
|
||||
entry = entry->next();
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
AdjustMethodEntries adjust(trace_name_printed);
|
||||
_local_table->do_safepoint_scan(adjust);
|
||||
}
|
||||
#endif // INCLUDE_JVMTI
|
||||
|
||||
// Verification and comp
|
||||
class VerifyCompResolvedMethod : StackObj {
|
||||
GrowableArray<oop>* _oops;
|
||||
public:
|
||||
size_t _errors;
|
||||
VerifyCompResolvedMethod(GrowableArray<oop>* oops) : _oops(oops), _errors(0) {}
|
||||
bool operator()(WeakHandle<vm_resolved_method_table_data>* val) {
|
||||
oop s = val->peek();
|
||||
if (s == NULL) {
|
||||
return true;
|
||||
}
|
||||
int len = _oops->length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
bool eq = s == _oops->at(i);
|
||||
assert(!eq, "Duplicate entries");
|
||||
if (eq) {
|
||||
_errors++;
|
||||
}
|
||||
}
|
||||
_oops->push(s);
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
size_t ResolvedMethodTable::items_count() {
|
||||
return _items_count;
|
||||
}
|
||||
|
||||
size_t ResolvedMethodTable::verify_and_compare_entries() {
|
||||
Thread* thr = Thread::current();
|
||||
GrowableArray<oop>* oops =
|
||||
new (ResourceObj::C_HEAP, mtInternal)
|
||||
GrowableArray<oop>((int)_current_size, true);
|
||||
|
||||
VerifyCompResolvedMethod vcs(oops);
|
||||
if (!_local_table->try_scan(thr, vcs)) {
|
||||
log_info(membername, table)("verify unavailable at this moment");
|
||||
}
|
||||
delete oops;
|
||||
return vcs._errors;
|
||||
}
|
||||
|
@ -25,89 +25,78 @@
|
||||
#ifndef SHARE_PRIMS_RESOLVEDMETHODTABLE_HPP
|
||||
#define SHARE_PRIMS_RESOLVEDMETHODTABLE_HPP
|
||||
|
||||
#include "gc/shared/oopStorage.hpp"
|
||||
#include "gc/shared/oopStorageParState.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "oops/symbol.hpp"
|
||||
#include "oops/weakHandle.hpp"
|
||||
#include "utilities/concurrentHashTable.hpp"
|
||||
#include "utilities/hashtable.hpp"
|
||||
|
||||
// Hashtable to record Method* used in ResolvedMethods, via. ResolvedMethod oops.
|
||||
// This is needed for redefinition to replace Method* with redefined versions.
|
||||
class ResolvedMethodTable;
|
||||
class ResolvedMethodTableConfig;
|
||||
typedef ConcurrentHashTable<WeakHandle<vm_resolved_method_table_data>, ResolvedMethodTableConfig, mtClass> ResolvedMethodTableHash;
|
||||
|
||||
// Entry in a ResolvedMethodTable, mapping a ClassLoaderWeakHandle for a single oop of
|
||||
// java_lang_invoke_ResolvedMethodName which holds JVM Method* in vmtarget.
|
||||
class ResolvedMethodTable : public AllStatic {
|
||||
static ResolvedMethodTableHash* _local_table;
|
||||
static size_t _current_size;
|
||||
|
||||
class ResolvedMethodEntry : public HashtableEntry<ClassLoaderWeakHandle, mtClass> {
|
||||
public:
|
||||
ResolvedMethodEntry* next() const {
|
||||
return (ResolvedMethodEntry*)HashtableEntry<ClassLoaderWeakHandle, mtClass>::next();
|
||||
}
|
||||
static OopStorage* _weak_handles;
|
||||
|
||||
ResolvedMethodEntry** next_addr() {
|
||||
return (ResolvedMethodEntry**)HashtableEntry<ClassLoaderWeakHandle, mtClass>::next_addr();
|
||||
}
|
||||
static volatile bool _has_work;
|
||||
|
||||
oop object();
|
||||
oop object_no_keepalive();
|
||||
|
||||
void print_on(outputStream* st) const;
|
||||
};
|
||||
|
||||
class ResolvedMethodTable : public Hashtable<ClassLoaderWeakHandle, mtClass> {
|
||||
enum Constants {
|
||||
_table_size = 1007
|
||||
};
|
||||
|
||||
static int _total_oops_removed;
|
||||
|
||||
static bool _dead_entries;
|
||||
|
||||
static ResolvedMethodTable* _the_table;
|
||||
private:
|
||||
ResolvedMethodEntry* bucket(int i) {
|
||||
return (ResolvedMethodEntry*) Hashtable<ClassLoaderWeakHandle, mtClass>::bucket(i);
|
||||
}
|
||||
|
||||
ResolvedMethodEntry** bucket_addr(int i) {
|
||||
return (ResolvedMethodEntry**) Hashtable<ClassLoaderWeakHandle, mtClass>::bucket_addr(i);
|
||||
}
|
||||
|
||||
unsigned int compute_hash(Method* method);
|
||||
|
||||
// need not be locked; no state change
|
||||
oop lookup(int index, unsigned int hash, Method* method);
|
||||
oop lookup(Method* method);
|
||||
|
||||
// must be done under ResolvedMethodTable_lock
|
||||
oop basic_add(Method* method, Handle rmethod_name);
|
||||
static volatile size_t _items_count;
|
||||
static volatile size_t _uncleaned_items_count;
|
||||
|
||||
public:
|
||||
ResolvedMethodTable();
|
||||
// Initialization
|
||||
static void create_table();
|
||||
|
||||
static void create_table() {
|
||||
assert(_the_table == NULL, "One symbol table allowed.");
|
||||
_the_table = new ResolvedMethodTable();
|
||||
}
|
||||
static size_t table_size();
|
||||
|
||||
// Called from java_lang_invoke_ResolvedMethodName
|
||||
static oop find_method(Method* method);
|
||||
static oop add_method(const methodHandle& method, Handle rmethod_name);
|
||||
// Lookup and inserts
|
||||
static oop find_method(const Method* method);
|
||||
static oop add_method(const Method* method, Handle rmethod_name);
|
||||
|
||||
static bool has_work() { return _dead_entries; }
|
||||
static void trigger_cleanup();
|
||||
// Callbacks
|
||||
static void item_added();
|
||||
static void item_removed();
|
||||
|
||||
static int removed_entries_count() { return _total_oops_removed; };
|
||||
// Cleaning
|
||||
static bool has_work();
|
||||
|
||||
#if INCLUDE_JVMTI
|
||||
// It is called at safepoint only for RedefineClasses
|
||||
static void adjust_method_entries(bool * trace_name_printed);
|
||||
#endif // INCLUDE_JVMTI
|
||||
// GC Support - Backing storage for the oop*s
|
||||
static OopStorage* weak_storage();
|
||||
|
||||
// Cleanup cleared entries
|
||||
static void unlink();
|
||||
// Cleaning and table management
|
||||
|
||||
#ifndef PRODUCT
|
||||
void print();
|
||||
#endif
|
||||
void verify();
|
||||
static double get_load_factor();
|
||||
static double get_dead_factor();
|
||||
|
||||
static void check_concurrent_work();
|
||||
static void trigger_concurrent_work();
|
||||
static void do_concurrent_work(JavaThread* jt);
|
||||
|
||||
static void grow(JavaThread* jt);
|
||||
static void clean_dead_entries(JavaThread* jt);
|
||||
|
||||
// GC Notification
|
||||
|
||||
// Must be called before a parallel walk where objects might die.
|
||||
static void reset_dead_counter();
|
||||
// After the parallel walk this method must be called to trigger
|
||||
// cleaning. Note it might trigger a resize instead.
|
||||
static void finish_dead_counter();
|
||||
// If GC uses ParState directly it should add the number of cleared
|
||||
// entries to this method.
|
||||
static void inc_dead_counter(size_t ndead);
|
||||
|
||||
// JVMTI Support - It is called at safepoint only for RedefineClasses
|
||||
JVMTI_ONLY(static void adjust_method_entries(bool * trace_name_printed);)
|
||||
|
||||
// Debugging
|
||||
static size_t items_count();
|
||||
static size_t verify_and_compare_entries();
|
||||
};
|
||||
|
||||
#endif // SHARE_PRIMS_RESOLVEDMETHODTABLE_HPP
|
||||
|
@ -2092,8 +2092,8 @@ WB_ENTRY(void, WB_DisableElfSectionCache(JNIEnv* env))
|
||||
#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 @@ static JNINativeMethod methods[] = {
|
||||
{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 },
|
||||
};
|
||||
|
@ -54,7 +54,8 @@ Mutex* StringTableWeakActive_lock = NULL;
|
||||
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 @@ void mutex_init() {
|
||||
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 @@ void mutex_init() {
|
||||
|
||||
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);
|
||||
|
@ -48,7 +48,8 @@ extern Mutex* StringTableWeakActive_lock; // STringTable weak storage act
|
||||
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
|
||||
|
@ -190,7 +190,7 @@ void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) {
|
||||
}
|
||||
|
||||
if (resolved_method_table_work) {
|
||||
ResolvedMethodTable::unlink();
|
||||
ResolvedMethodTable::do_concurrent_work(jt);
|
||||
}
|
||||
|
||||
if (protection_domain_table_work) {
|
||||
|
@ -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.code.Compiler;
|
||||
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 class MemberNameLeak {
|
||||
public static void main(String[] args) throws Throwable {
|
||||
Leak leak = new Leak();
|
||||
WhiteBox wb = WhiteBox.getWhiteBox();
|
||||
int removedCountOrig = wb.resolvedMethodRemovedCount();
|
||||
int removedCount;
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
MethodType mt = MethodType.fromMethodDescriptorString("()V", Leak.class.getClassLoader());
|
||||
ClassWithManyMethodsClassLoader classLoader = new ClassWithManyMethodsClassLoader();
|
||||
Class<?> clazz = classLoader.create(className, methodPrefix, methodCount);
|
||||
|
||||
long before = wb.resolvedMethodItemsCount();
|
||||
|
||||
Object o = clazz.newInstance();
|
||||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup());
|
||||
|
||||
for (int i = 0; i < methodCount; i++) {
|
||||
MethodType mt = MethodType.fromMethodDescriptorString("()V", classLoader);
|
||||
String methodName = methodPrefix + i;
|
||||
// findSpecial leaks some native mem
|
||||
MethodHandle mh = lookup.findSpecial(Leak.class, "callMe", mt, Leak.class);
|
||||
mh.invokeExact(leak);
|
||||
// Add entry to ResolvedMethodTable.
|
||||
MethodHandle mh0 = lookup.findSpecial(clazz, methodName, mt, clazz);
|
||||
// Find entry in ResolvedMethodTable.
|
||||
MethodHandle mh1 = lookup.findSpecial(clazz, methodName, mt, clazz);
|
||||
|
||||
mh1.invoke(o);
|
||||
}
|
||||
|
||||
long after = wb.resolvedMethodItemsCount();
|
||||
|
||||
if (after == before) {
|
||||
throw new RuntimeException("Too few resolved methods");
|
||||
}
|
||||
|
||||
// Wait until ServiceThread cleans ResolvedMethod table
|
||||
@ -64,16 +88,19 @@ public class MemberNameLeak {
|
||||
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 @@ public class MemberNameLeak {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -541,7 +541,7 @@ public class WhiteBox {
|
||||
public native void disableElfSectionCache();
|
||||
|
||||
// Resolved Method Table
|
||||
public native int resolvedMethodRemovedCount();
|
||||
public native long resolvedMethodItemsCount();
|
||||
|
||||
// Protection Domain Table
|
||||
public native int protectionDomainRemovedCount();
|
||||
|
Loading…
x
Reference in New Issue
Block a user