8221393: ResolvedMethodTable too small for StackWalking applications

Reviewed-by: coleenp, rehn
This commit is contained in:
Stefan Karlsson 2019-04-10 15:41:04 +02:00
parent 52c427ae20
commit 23f02171c8
20 changed files with 714 additions and 307 deletions

@ -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();