8184765: Dynamically resize SystemDictionary
Implemented dynamic resizing, which triggers when load factor is too high Reviewed-by: coleenp, rehn
This commit is contained in:
parent
e878b3272b
commit
a043febf6f
@ -604,40 +604,27 @@ ModuleEntryTable* ClassLoaderData::modules() {
|
||||
|
||||
const int _boot_loader_dictionary_size = 1009;
|
||||
const int _default_loader_dictionary_size = 107;
|
||||
const int _prime_array_size = 8; // array of primes for system dictionary size
|
||||
const int _average_depth_goal = 3; // goal for lookup length
|
||||
const int _primelist[_prime_array_size] = {107, 1009, 2017, 4049, 5051, 10103, 20201, 40423};
|
||||
|
||||
// Calculate a "good" dictionary size based
|
||||
// on predicted or current loaded classes count.
|
||||
static int calculate_dictionary_size(int classcount) {
|
||||
int newsize = _primelist[0];
|
||||
if (classcount > 0 && !DumpSharedSpaces) {
|
||||
int index = 0;
|
||||
int desiredsize = classcount/_average_depth_goal;
|
||||
for (newsize = _primelist[index]; index < _prime_array_size -1;
|
||||
newsize = _primelist[++index]) {
|
||||
if (desiredsize <= newsize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return newsize;
|
||||
}
|
||||
|
||||
Dictionary* ClassLoaderData::create_dictionary() {
|
||||
assert(!is_anonymous(), "anonymous class loader data do not have a dictionary");
|
||||
int size;
|
||||
bool resizable = false;
|
||||
if (_the_null_class_loader_data == NULL) {
|
||||
size = _boot_loader_dictionary_size;
|
||||
resizable = true;
|
||||
} else if (class_loader()->is_a(SystemDictionary::reflect_DelegatingClassLoader_klass())) {
|
||||
size = 1; // there's only one class in relection class loader and no initiated classes
|
||||
} else if (is_system_class_loader_data()) {
|
||||
size = calculate_dictionary_size(PredictedLoadedClassCount);
|
||||
size = _boot_loader_dictionary_size;
|
||||
resizable = true;
|
||||
} else {
|
||||
size = _default_loader_dictionary_size;
|
||||
resizable = true;
|
||||
}
|
||||
return new Dictionary(this, size);
|
||||
if (!DynamicallyResizeSystemDictionaries || DumpSharedSpaces || UseSharedSpaces) {
|
||||
resizable = false;
|
||||
}
|
||||
return new Dictionary(this, size, resizable);
|
||||
}
|
||||
|
||||
// Unloading support
|
||||
@ -1325,6 +1312,19 @@ void ClassLoaderDataGraph::purge() {
|
||||
}
|
||||
}
|
||||
|
||||
int ClassLoaderDataGraph::resize_if_needed() {
|
||||
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint!");
|
||||
int resized = 0;
|
||||
if (Dictionary::does_any_dictionary_needs_resizing()) {
|
||||
FOR_ALL_DICTIONARY(cld) {
|
||||
if (cld->dictionary()->resize_if_needed()) {
|
||||
resized++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
void ClassLoaderDataGraph::post_class_unload_events() {
|
||||
#if INCLUDE_TRACE
|
||||
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint!");
|
||||
|
@ -143,6 +143,8 @@ class ClassLoaderDataGraph : public AllStatic {
|
||||
}
|
||||
}
|
||||
|
||||
static int resize_if_needed();
|
||||
|
||||
static bool has_metaspace_oom() { return _metaspace_oom; }
|
||||
static void set_metaspace_oom(bool value) { _metaspace_oom = value; }
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "classfile/protectionDomainCache.hpp"
|
||||
#include "classfile/systemDictionary.hpp"
|
||||
#include "classfile/systemDictionaryShared.hpp"
|
||||
#include "gc/shared/gcLocker.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "logging/logStream.hpp"
|
||||
#include "memory/iterator.hpp"
|
||||
@ -39,6 +40,11 @@
|
||||
#include "runtime/orderAccess.inline.hpp"
|
||||
#include "utilities/hashtable.inline.hpp"
|
||||
|
||||
// Optimization: if any dictionary needs resizing, we set this flag,
|
||||
// so that we dont't have to walk all dictionaries to check if any actually
|
||||
// needs resizing, which is costly to do at Safepoint.
|
||||
bool Dictionary::_some_dictionary_needs_resizing = false;
|
||||
|
||||
size_t Dictionary::entry_size() {
|
||||
if (DumpSharedSpaces) {
|
||||
return SystemDictionaryShared::dictionary_entry_size();
|
||||
@ -47,15 +53,17 @@ size_t Dictionary::entry_size() {
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary::Dictionary(ClassLoaderData* loader_data, int table_size)
|
||||
: _loader_data(loader_data), Hashtable<InstanceKlass*, mtClass>(table_size, (int)entry_size()) {
|
||||
Dictionary::Dictionary(ClassLoaderData* loader_data, int table_size, bool resizable)
|
||||
: _loader_data(loader_data), _resizable(resizable), _needs_resizing(false),
|
||||
Hashtable<InstanceKlass*, mtClass>(table_size, (int)entry_size()) {
|
||||
};
|
||||
|
||||
|
||||
Dictionary::Dictionary(ClassLoaderData* loader_data,
|
||||
int table_size, HashtableBucket<mtClass>* t,
|
||||
int number_of_entries)
|
||||
: _loader_data(loader_data), Hashtable<InstanceKlass*, mtClass>(table_size, (int)entry_size(), t, number_of_entries) {
|
||||
int number_of_entries, bool resizable)
|
||||
: _loader_data(loader_data), _resizable(resizable), _needs_resizing(false),
|
||||
Hashtable<InstanceKlass*, mtClass>(table_size, (int)entry_size(), t, number_of_entries) {
|
||||
};
|
||||
|
||||
Dictionary::~Dictionary() {
|
||||
@ -96,6 +104,60 @@ void Dictionary::free_entry(DictionaryEntry* entry) {
|
||||
FREE_C_HEAP_ARRAY(char, entry);
|
||||
}
|
||||
|
||||
const int _resize_load_trigger = 5; // load factor that will trigger the resize
|
||||
const double _resize_factor = 2.0; // by how much we will resize using current number of entries
|
||||
const int _resize_max_size = 40423; // the max dictionary size allowed
|
||||
const int _primelist[] = {107, 1009, 2017, 4049, 5051, 10103, 20201, _resize_max_size};
|
||||
const int _prime_array_size = sizeof(_primelist)/sizeof(int);
|
||||
|
||||
// Calculate next "good" dictionary size based on requested count
|
||||
static int calculate_dictionary_size(int requested) {
|
||||
int newsize = _primelist[0];
|
||||
int index = 0;
|
||||
for (newsize = _primelist[index]; index < (_prime_array_size - 1);
|
||||
newsize = _primelist[++index]) {
|
||||
if (requested <= newsize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return newsize;
|
||||
}
|
||||
|
||||
bool Dictionary::does_any_dictionary_needs_resizing() {
|
||||
return Dictionary::_some_dictionary_needs_resizing;
|
||||
}
|
||||
|
||||
void Dictionary::check_if_needs_resize() {
|
||||
if (_resizable == true) {
|
||||
if (number_of_entries() > (_resize_load_trigger*table_size())) {
|
||||
_needs_resizing = true;
|
||||
Dictionary::_some_dictionary_needs_resizing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Dictionary::resize_if_needed() {
|
||||
int desired_size = 0;
|
||||
if (_needs_resizing == true) {
|
||||
desired_size = calculate_dictionary_size((int)(_resize_factor*number_of_entries()));
|
||||
if (desired_size >= _resize_max_size) {
|
||||
desired_size = _resize_max_size;
|
||||
// We have reached the limit, turn resizing off
|
||||
_resizable = false;
|
||||
}
|
||||
if ((desired_size != 0) && (desired_size != table_size())) {
|
||||
if (!resize(desired_size)) {
|
||||
// Something went wrong, turn resizing off
|
||||
_resizable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_needs_resizing = false;
|
||||
Dictionary::_some_dictionary_needs_resizing = false;
|
||||
|
||||
return (desired_size != 0);
|
||||
}
|
||||
|
||||
bool DictionaryEntry::contains_protection_domain(oop protection_domain) const {
|
||||
#ifdef ASSERT
|
||||
@ -264,14 +326,16 @@ void Dictionary::classes_do(MetaspaceClosure* it) {
|
||||
// also cast to volatile; we do this to ensure store order is maintained
|
||||
// by the compilers.
|
||||
|
||||
void Dictionary::add_klass(int index, unsigned int hash, Symbol* class_name,
|
||||
void Dictionary::add_klass(unsigned int hash, Symbol* class_name,
|
||||
InstanceKlass* obj) {
|
||||
assert_locked_or_safepoint(SystemDictionary_lock);
|
||||
assert(obj != NULL, "adding NULL obj");
|
||||
assert(obj->name() == class_name, "sanity check on name");
|
||||
|
||||
DictionaryEntry* entry = new_entry(hash, obj);
|
||||
int index = hash_to_index(hash);
|
||||
add_entry(index, entry);
|
||||
check_if_needs_resize();
|
||||
}
|
||||
|
||||
|
||||
@ -299,8 +363,11 @@ DictionaryEntry* Dictionary::get_entry(int index, unsigned int hash,
|
||||
}
|
||||
|
||||
|
||||
InstanceKlass* Dictionary::find(int index, unsigned int hash, Symbol* name,
|
||||
InstanceKlass* Dictionary::find(unsigned int hash, Symbol* name,
|
||||
Handle protection_domain) {
|
||||
NoSafepointVerifier nsv;
|
||||
|
||||
int index = hash_to_index(hash);
|
||||
DictionaryEntry* entry = get_entry(index, hash, name);
|
||||
if (entry != NULL && entry->is_valid_protection_domain(protection_domain)) {
|
||||
return entry->instance_klass();
|
||||
@ -350,9 +417,10 @@ void Dictionary::add_protection_domain(int index, unsigned int hash,
|
||||
}
|
||||
|
||||
|
||||
bool Dictionary::is_valid_protection_domain(int index, unsigned int hash,
|
||||
bool Dictionary::is_valid_protection_domain(unsigned int hash,
|
||||
Symbol* name,
|
||||
Handle protection_domain) {
|
||||
int index = hash_to_index(hash);
|
||||
DictionaryEntry* entry = get_entry(index, hash, name);
|
||||
return entry->is_valid_protection_domain(protection_domain);
|
||||
}
|
||||
|
@ -43,6 +43,11 @@ class BoolObjectClosure;
|
||||
class Dictionary : public Hashtable<InstanceKlass*, mtClass> {
|
||||
friend class VMStructs;
|
||||
|
||||
static bool _some_dictionary_needs_resizing;
|
||||
bool _resizable;
|
||||
bool _needs_resizing;
|
||||
void check_if_needs_resize();
|
||||
|
||||
ClassLoaderData* _loader_data; // backpointer to owning loader
|
||||
ClassLoaderData* loader_data() const { return _loader_data; }
|
||||
|
||||
@ -51,13 +56,16 @@ class Dictionary : public Hashtable<InstanceKlass*, mtClass> {
|
||||
protected:
|
||||
static size_t entry_size();
|
||||
public:
|
||||
Dictionary(ClassLoaderData* loader_data, int table_size);
|
||||
Dictionary(ClassLoaderData* loader_data, int table_size, HashtableBucket<mtClass>* t, int number_of_entries);
|
||||
Dictionary(ClassLoaderData* loader_data, int table_size, bool resizable = false);
|
||||
Dictionary(ClassLoaderData* loader_data, int table_size, HashtableBucket<mtClass>* t, int number_of_entries, bool resizable = false);
|
||||
~Dictionary();
|
||||
|
||||
static bool does_any_dictionary_needs_resizing();
|
||||
bool resize_if_needed();
|
||||
|
||||
DictionaryEntry* new_entry(unsigned int hash, InstanceKlass* klass);
|
||||
|
||||
void add_klass(int index, unsigned int hash, Symbol* class_name, InstanceKlass* obj);
|
||||
void add_klass(unsigned int hash, Symbol* class_name, InstanceKlass* obj);
|
||||
|
||||
InstanceKlass* find_class(int index, unsigned int hash, Symbol* name);
|
||||
|
||||
@ -79,8 +87,8 @@ public:
|
||||
void do_unloading();
|
||||
|
||||
// Protection domains
|
||||
InstanceKlass* find(int index, unsigned int hash, Symbol* name, Handle protection_domain);
|
||||
bool is_valid_protection_domain(int index, unsigned int hash,
|
||||
InstanceKlass* find(unsigned int hash, Symbol* name, Handle protection_domain);
|
||||
bool is_valid_protection_domain(unsigned int hash,
|
||||
Symbol* name,
|
||||
Handle protection_domain);
|
||||
void add_protection_domain(int index, unsigned int hash,
|
||||
|
@ -371,7 +371,6 @@ Klass* SystemDictionary::resolve_super_or_fail(Symbol* child_name,
|
||||
ClassLoaderData* loader_data = class_loader_data(class_loader);
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
unsigned int d_hash = dictionary->compute_hash(child_name);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
unsigned int p_hash = placeholders()->compute_hash(child_name);
|
||||
int p_index = placeholders()->hash_to_index(p_hash);
|
||||
// can't throw error holding a lock
|
||||
@ -379,7 +378,7 @@ Klass* SystemDictionary::resolve_super_or_fail(Symbol* child_name,
|
||||
bool throw_circularity_error = false;
|
||||
{
|
||||
MutexLocker mu(SystemDictionary_lock, THREAD);
|
||||
Klass* childk = find_class(d_index, d_hash, child_name, dictionary);
|
||||
Klass* childk = find_class(d_hash, child_name, dictionary);
|
||||
Klass* quicksuperk;
|
||||
// to support // loading: if child done loading, just return superclass
|
||||
// if class_name, & class_loader don't match:
|
||||
@ -487,9 +486,9 @@ void SystemDictionary::validate_protection_domain(InstanceKlass* klass,
|
||||
|
||||
Symbol* kn = klass->name();
|
||||
unsigned int d_hash = dictionary->compute_hash(kn);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
|
||||
MutexLocker mu(SystemDictionary_lock, THREAD);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
dictionary->add_protection_domain(d_index, d_hash, klass,
|
||||
protection_domain, THREAD);
|
||||
}
|
||||
@ -555,7 +554,6 @@ InstanceKlass* SystemDictionary::handle_parallel_super_load(
|
||||
ClassLoaderData* loader_data = class_loader_data(class_loader);
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
unsigned int d_hash = dictionary->compute_hash(name);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
unsigned int p_hash = placeholders()->compute_hash(name);
|
||||
int p_index = placeholders()->hash_to_index(p_hash);
|
||||
|
||||
@ -579,7 +577,7 @@ InstanceKlass* SystemDictionary::handle_parallel_super_load(
|
||||
if (!class_loader.is_null() && is_parallelCapable(class_loader)) {
|
||||
MutexLocker mu(SystemDictionary_lock, THREAD);
|
||||
// Check if classloading completed while we were loading superclass or waiting
|
||||
return find_class(d_index, d_hash, name, dictionary);
|
||||
return find_class(d_hash, name, dictionary);
|
||||
}
|
||||
|
||||
// must loop to both handle other placeholder updates
|
||||
@ -589,7 +587,7 @@ InstanceKlass* SystemDictionary::handle_parallel_super_load(
|
||||
while (super_load_in_progress) {
|
||||
MutexLocker mu(SystemDictionary_lock, THREAD);
|
||||
// Check if classloading completed while we were loading superclass or waiting
|
||||
InstanceKlass* check = find_class(d_index, d_hash, name, dictionary);
|
||||
InstanceKlass* check = find_class(d_hash, name, dictionary);
|
||||
if (check != NULL) {
|
||||
// Klass is already loaded, so just return it
|
||||
return check;
|
||||
@ -670,6 +668,7 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));
|
||||
ClassLoaderData *loader_data = register_loader(class_loader, CHECK_NULL);
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
unsigned int d_hash = dictionary->compute_hash(name);
|
||||
|
||||
// Do lookup to see if class already exist and the protection domain
|
||||
// has the right access
|
||||
@ -677,11 +676,10 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
// All subsequent calls use find_class, and set has_loaded_class so that
|
||||
// before we return a result we call out to java to check for valid protection domain
|
||||
// to allow returning the Klass* and add it to the pd_set if it is valid
|
||||
unsigned int d_hash = dictionary->compute_hash(name);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
Klass* probe = dictionary->find(d_index, d_hash, name, protection_domain);
|
||||
if (probe != NULL) return probe;
|
||||
|
||||
{
|
||||
Klass* probe = dictionary->find(d_hash, name, protection_domain);
|
||||
if (probe != NULL) return probe;
|
||||
}
|
||||
|
||||
// Non-bootstrap class loaders will call out to class loader and
|
||||
// define via jvm/jni_DefineClass which will acquire the
|
||||
@ -716,7 +714,7 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
|
||||
{
|
||||
MutexLocker mu(SystemDictionary_lock, THREAD);
|
||||
InstanceKlass* check = find_class(d_index, d_hash, name, dictionary);
|
||||
InstanceKlass* check = find_class(d_hash, name, dictionary);
|
||||
if (check != NULL) {
|
||||
// Klass is already loaded, so just return it
|
||||
class_has_been_loaded = true;
|
||||
@ -800,7 +798,7 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
double_lock_wait(lockObject, THREAD);
|
||||
}
|
||||
// Check if classloading completed while we were waiting
|
||||
InstanceKlass* check = find_class(d_index, d_hash, name, dictionary);
|
||||
InstanceKlass* check = find_class(d_hash, name, dictionary);
|
||||
if (check != NULL) {
|
||||
// Klass is already loaded, so just return it
|
||||
k = check;
|
||||
@ -825,7 +823,7 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
// i.e. now that we hold the LOAD_INSTANCE token on loading this class/CL
|
||||
// one final check if the load has already completed
|
||||
// class loaders holding the ObjectLock shouldn't find the class here
|
||||
InstanceKlass* check = find_class(d_index, d_hash, name, dictionary);
|
||||
InstanceKlass* check = find_class(d_hash, name, dictionary);
|
||||
if (check != NULL) {
|
||||
// Klass is already loaded, so return it after checking/adding protection domain
|
||||
k = check;
|
||||
@ -858,7 +856,7 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
if (k == NULL && HAS_PENDING_EXCEPTION
|
||||
&& PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass())) {
|
||||
MutexLocker mu(SystemDictionary_lock, THREAD);
|
||||
InstanceKlass* check = find_class(d_index, d_hash, name, dictionary);
|
||||
InstanceKlass* check = find_class(d_hash, name, dictionary);
|
||||
if (check != NULL) {
|
||||
// Klass is already loaded, so just use it
|
||||
k = check;
|
||||
@ -873,7 +871,7 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
if (!HAS_PENDING_EXCEPTION && k != NULL &&
|
||||
k->class_loader() != class_loader()) {
|
||||
|
||||
check_constraints(d_index, d_hash, k, class_loader, false, THREAD);
|
||||
check_constraints(d_hash, k, class_loader, false, THREAD);
|
||||
|
||||
// Need to check for a PENDING_EXCEPTION again; check_constraints
|
||||
// can throw and doesn't use the CHECK macro.
|
||||
@ -881,7 +879,7 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
{ // Grabbing the Compile_lock prevents systemDictionary updates
|
||||
// during compilations.
|
||||
MutexLocker mu(Compile_lock, THREAD);
|
||||
update_dictionary(d_index, d_hash, p_index, p_hash,
|
||||
update_dictionary(d_hash, p_index, p_hash,
|
||||
k, class_loader, THREAD);
|
||||
}
|
||||
|
||||
@ -923,7 +921,7 @@ Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
|
||||
if (protection_domain() == NULL) return k;
|
||||
|
||||
// Check the protection domain has the right access
|
||||
if (dictionary->is_valid_protection_domain(d_index, d_hash, name,
|
||||
if (dictionary->is_valid_protection_domain(d_hash, name,
|
||||
protection_domain)) {
|
||||
return k;
|
||||
}
|
||||
@ -965,8 +963,7 @@ Klass* SystemDictionary::find(Symbol* class_name,
|
||||
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
unsigned int d_hash = dictionary->compute_hash(class_name);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
return dictionary->find(d_index, d_hash, class_name,
|
||||
return dictionary->find(d_hash, class_name,
|
||||
protection_domain);
|
||||
}
|
||||
|
||||
@ -1644,8 +1641,7 @@ void SystemDictionary::define_instance_class(InstanceKlass* k, TRAPS) {
|
||||
Symbol* name_h = k->name();
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
unsigned int d_hash = dictionary->compute_hash(name_h);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
check_constraints(d_index, d_hash, k, class_loader_h, true, CHECK);
|
||||
check_constraints(d_hash, k, class_loader_h, true, CHECK);
|
||||
|
||||
// Register class just loaded with class loader (placed in Vector)
|
||||
// Note we do this before updating the dictionary, as this can
|
||||
@ -1673,7 +1669,7 @@ void SystemDictionary::define_instance_class(InstanceKlass* k, TRAPS) {
|
||||
|
||||
// Add to systemDictionary - so other classes can see it.
|
||||
// Grabs and releases SystemDictionary_lock
|
||||
update_dictionary(d_index, d_hash, p_index, p_hash,
|
||||
update_dictionary(d_hash, p_index, p_hash,
|
||||
k, class_loader_h, THREAD);
|
||||
}
|
||||
k->eager_initialize(THREAD);
|
||||
@ -1715,7 +1711,6 @@ InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_nam
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
|
||||
unsigned int d_hash = dictionary->compute_hash(name_h);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
|
||||
// Hold SD lock around find_class and placeholder creation for DEFINE_CLASS
|
||||
unsigned int p_hash = placeholders()->compute_hash(name_h);
|
||||
@ -1726,7 +1721,7 @@ InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_nam
|
||||
MutexLocker mu(SystemDictionary_lock, THREAD);
|
||||
// First check if class already defined
|
||||
if (UnsyncloadClass || (is_parallelDefine(class_loader))) {
|
||||
InstanceKlass* check = find_class(d_index, d_hash, name_h, dictionary);
|
||||
InstanceKlass* check = find_class(d_hash, name_h, dictionary);
|
||||
if (check != NULL) {
|
||||
return check;
|
||||
}
|
||||
@ -1748,7 +1743,7 @@ InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_nam
|
||||
placeholders()->find_and_remove(p_index, p_hash, name_h, loader_data, PlaceholderTable::DEFINE_CLASS, THREAD);
|
||||
SystemDictionary_lock->notify_all();
|
||||
#ifdef ASSERT
|
||||
InstanceKlass* check = find_class(d_index, d_hash, name_h, dictionary);
|
||||
InstanceKlass* check = find_class(d_hash, name_h, dictionary);
|
||||
assert(check != NULL, "definer missed recording success");
|
||||
#endif
|
||||
return probe->instance_klass();
|
||||
@ -1823,10 +1818,11 @@ void SystemDictionary::check_loader_lock_contention(Handle loader_lock, TRAPS) {
|
||||
// ----------------------------------------------------------------------------
|
||||
// Lookup
|
||||
|
||||
InstanceKlass* SystemDictionary::find_class(int index, unsigned int hash,
|
||||
InstanceKlass* SystemDictionary::find_class(unsigned int hash,
|
||||
Symbol* class_name,
|
||||
Dictionary* dictionary) {
|
||||
assert_locked_or_safepoint(SystemDictionary_lock);
|
||||
int index = dictionary->hash_to_index(hash);
|
||||
return dictionary->find_class(index, hash, class_name);
|
||||
}
|
||||
|
||||
@ -1856,8 +1852,7 @@ InstanceKlass* SystemDictionary::find_class(Symbol* class_name, ClassLoaderData*
|
||||
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
unsigned int d_hash = dictionary->compute_hash(class_name);
|
||||
int d_index = dictionary->hash_to_index(d_hash);
|
||||
return find_class(d_index, d_hash, class_name, dictionary);
|
||||
return find_class(d_hash, class_name, dictionary);
|
||||
}
|
||||
|
||||
|
||||
@ -2210,7 +2205,7 @@ BasicType SystemDictionary::box_klass_type(Klass* k) {
|
||||
// if defining is true, then LinkageError if already in dictionary
|
||||
// if initiating loader, then ok if InstanceKlass matches existing entry
|
||||
|
||||
void SystemDictionary::check_constraints(int d_index, unsigned int d_hash,
|
||||
void SystemDictionary::check_constraints(unsigned int d_hash,
|
||||
InstanceKlass* k,
|
||||
Handle class_loader, bool defining,
|
||||
TRAPS) {
|
||||
@ -2222,7 +2217,7 @@ void SystemDictionary::check_constraints(int d_index, unsigned int d_hash,
|
||||
|
||||
MutexLocker mu(SystemDictionary_lock, THREAD);
|
||||
|
||||
InstanceKlass* check = find_class(d_index, d_hash, name, loader_data->dictionary());
|
||||
InstanceKlass* check = find_class(d_hash, name, loader_data->dictionary());
|
||||
if (check != NULL) {
|
||||
// if different InstanceKlass - duplicate class definition,
|
||||
// else - ok, class loaded by a different thread in parallel,
|
||||
@ -2270,7 +2265,7 @@ void SystemDictionary::check_constraints(int d_index, unsigned int d_hash,
|
||||
|
||||
// Update class loader data dictionary - done after check_constraint and add_to_hierachy
|
||||
// have been called.
|
||||
void SystemDictionary::update_dictionary(int d_index, unsigned int d_hash,
|
||||
void SystemDictionary::update_dictionary(unsigned int d_hash,
|
||||
int p_index, unsigned int p_hash,
|
||||
InstanceKlass* k,
|
||||
Handle class_loader,
|
||||
@ -2305,13 +2300,13 @@ void SystemDictionary::update_dictionary(int d_index, unsigned int d_hash,
|
||||
|
||||
// Make a new dictionary entry.
|
||||
Dictionary* dictionary = loader_data->dictionary();
|
||||
InstanceKlass* sd_check = find_class(d_index, d_hash, name, dictionary);
|
||||
InstanceKlass* sd_check = find_class(d_hash, name, dictionary);
|
||||
if (sd_check == NULL) {
|
||||
dictionary->add_klass(d_index, d_hash, name, k);
|
||||
dictionary->add_klass(d_hash, name, k);
|
||||
notice_modification();
|
||||
}
|
||||
#ifdef ASSERT
|
||||
sd_check = find_class(d_index, d_hash, name, dictionary);
|
||||
sd_check = find_class(d_hash, name, dictionary);
|
||||
assert (sd_check != NULL, "should have entry in dictionary");
|
||||
// Note: there may be a placeholder entry: for circularity testing
|
||||
// or for parallel defines
|
||||
@ -2388,16 +2383,14 @@ bool SystemDictionary::add_loader_constraint(Symbol* class_name,
|
||||
|
||||
Dictionary* dictionary1 = loader_data1->dictionary();
|
||||
unsigned int d_hash1 = dictionary1->compute_hash(constraint_name);
|
||||
int d_index1 = dictionary1->hash_to_index(d_hash1);
|
||||
|
||||
Dictionary* dictionary2 = loader_data2->dictionary();
|
||||
unsigned int d_hash2 = dictionary2->compute_hash(constraint_name);
|
||||
int d_index2 = dictionary2->hash_to_index(d_hash2);
|
||||
|
||||
{
|
||||
MutexLocker mu_s(SystemDictionary_lock, THREAD);
|
||||
InstanceKlass* klass1 = find_class(d_index1, d_hash1, constraint_name, dictionary1);
|
||||
InstanceKlass* klass2 = find_class(d_index2, d_hash2, constraint_name, dictionary2);
|
||||
InstanceKlass* klass1 = find_class(d_hash1, constraint_name, dictionary1);
|
||||
InstanceKlass* klass2 = find_class(d_hash2, constraint_name, dictionary2);
|
||||
return constraints()->add_entry(constraint_name, klass1, class_loader1,
|
||||
klass2, class_loader2);
|
||||
}
|
||||
|
@ -655,11 +655,8 @@ protected:
|
||||
// Setup link to hierarchy
|
||||
static void add_to_hierarchy(InstanceKlass* k, TRAPS);
|
||||
|
||||
// We pass in the hashtable index so we can calculate it outside of
|
||||
// the SystemDictionary_lock.
|
||||
|
||||
// Basic find on loaded classes
|
||||
static InstanceKlass* find_class(int index, unsigned int hash,
|
||||
static InstanceKlass* find_class(unsigned int hash,
|
||||
Symbol* name, Dictionary* dictionary);
|
||||
static InstanceKlass* find_class(Symbol* class_name, ClassLoaderData* loader_data);
|
||||
|
||||
@ -685,10 +682,10 @@ protected:
|
||||
static void initialize_preloaded_classes(TRAPS);
|
||||
|
||||
// Class loader constraints
|
||||
static void check_constraints(int index, unsigned int hash,
|
||||
static void check_constraints(unsigned int hash,
|
||||
InstanceKlass* k, Handle loader,
|
||||
bool defining, TRAPS);
|
||||
static void update_dictionary(int d_index, unsigned int d_hash,
|
||||
static void update_dictionary(unsigned int d_hash,
|
||||
int p_index, unsigned int p_hash,
|
||||
InstanceKlass* k, Handle loader,
|
||||
TRAPS);
|
||||
|
@ -1144,8 +1144,8 @@ public:
|
||||
notproduct(bool, PrintSystemDictionaryAtExit, false, \
|
||||
"Print the system dictionary at exit") \
|
||||
\
|
||||
experimental(intx, PredictedLoadedClassCount, 0, \
|
||||
"Experimental: Tune loaded class cache starting size") \
|
||||
diagnostic(bool, DynamicallyResizeSystemDictionaries, true, \
|
||||
"Dynamically resize system dictionaries as needed") \
|
||||
\
|
||||
diagnostic(bool, UnsyncloadClass, false, \
|
||||
"Unstable: VM calls loadClass unsynchronized. Custom " \
|
||||
|
@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "classfile/classLoaderData.hpp"
|
||||
#include "classfile/stringTable.hpp"
|
||||
#include "classfile/symbolTable.hpp"
|
||||
#include "classfile/systemDictionary.hpp"
|
||||
@ -618,6 +619,14 @@ public:
|
||||
ClassLoaderDataGraph::purge_if_needed();
|
||||
event_safepoint_cleanup_task_commit(event, name);
|
||||
}
|
||||
|
||||
if (!_subtasks.is_task_claimed(SafepointSynchronize::SAFEPOINT_CLEANUP_SYSTEM_DICTIONARY_RESIZE)) {
|
||||
const char* name = "resizing system dictionaries";
|
||||
EventSafepointCleanupTask event;
|
||||
TraceTime timer(name, TRACETIME_LOG(Info, safepoint, cleanup));
|
||||
ClassLoaderDataGraph::resize_if_needed();
|
||||
event_safepoint_cleanup_task_commit(event, name);
|
||||
}
|
||||
_subtasks.all_tasks_completed(_num_workers);
|
||||
}
|
||||
};
|
||||
|
@ -83,6 +83,7 @@ class SafepointSynchronize : AllStatic {
|
||||
SAFEPOINT_CLEANUP_SYMBOL_TABLE_REHASH,
|
||||
SAFEPOINT_CLEANUP_STRING_TABLE_REHASH,
|
||||
SAFEPOINT_CLEANUP_CLD_PURGE,
|
||||
SAFEPOINT_CLEANUP_SYSTEM_DICTIONARY_RESIZE,
|
||||
// Leave this one last.
|
||||
SAFEPOINT_CLEANUP_NUM_TASKS
|
||||
};
|
||||
|
@ -264,6 +264,49 @@ static int literal_size(oop obj) {
|
||||
}
|
||||
}
|
||||
|
||||
template <MEMFLAGS F> bool BasicHashtable<F>::resize(int new_size) {
|
||||
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
|
||||
|
||||
// Allocate new buckets
|
||||
HashtableBucket<F>* buckets_new = NEW_C_HEAP_ARRAY2_RETURN_NULL(HashtableBucket<F>, new_size, F, CURRENT_PC);
|
||||
if (buckets_new == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the new buckets
|
||||
for (int i = 0; i < new_size; i++) {
|
||||
buckets_new[i].clear();
|
||||
}
|
||||
|
||||
int table_size_old = _table_size;
|
||||
// hash_to_index() uses _table_size, so switch the sizes now
|
||||
_table_size = new_size;
|
||||
|
||||
// Move entries from the old table to a new table
|
||||
for (int index_old = 0; index_old < table_size_old; index_old++) {
|
||||
for (BasicHashtableEntry<F>* p = _buckets[index_old].get_entry(); p != NULL; ) {
|
||||
BasicHashtableEntry<F>* next = p->next();
|
||||
bool keep_shared = p->is_shared();
|
||||
int index_new = hash_to_index(p->hash());
|
||||
|
||||
p->set_next(buckets_new[index_new].get_entry());
|
||||
buckets_new[index_new].set_entry(p);
|
||||
|
||||
if (keep_shared) {
|
||||
p->set_shared();
|
||||
}
|
||||
p = next;
|
||||
}
|
||||
}
|
||||
|
||||
// The old backets now can be released
|
||||
BasicHashtable<F>::free_buckets();
|
||||
|
||||
// Switch to the new storage
|
||||
_buckets = buckets_new;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Dump footprint and bucket length statistics
|
||||
//
|
||||
|
@ -237,6 +237,8 @@ public:
|
||||
|
||||
int number_of_entries() const { return _number_of_entries; }
|
||||
|
||||
bool resize(int new_size);
|
||||
|
||||
template <class T> void verify_table(const char* table_name) PRODUCT_RETURN;
|
||||
};
|
||||
|
||||
@ -281,7 +283,6 @@ public:
|
||||
HashtableEntry<T, F>** bucket_addr(int i) {
|
||||
return (HashtableEntry<T, F>**)BasicHashtable<F>::bucket_addr(i);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <class T, MEMFLAGS F> class RehashableHashtable : public Hashtable<T, F> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2017, 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
|
||||
@ -36,9 +36,9 @@ import jdk.test.lib.Platform;
|
||||
|
||||
public class VMOptionWarning {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-XX:+PredictedLoadedClassCount", "-version");
|
||||
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-XX:+AlwaysSafeConstructors", "-version");
|
||||
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
||||
output.shouldContain("Error: VM option 'PredictedLoadedClassCount' is experimental and must be enabled via -XX:+UnlockExperimentalVMOptions.");
|
||||
output.shouldContain("Error: VM option 'AlwaysSafeConstructors' is experimental and must be enabled via -XX:+UnlockExperimentalVMOptions.");
|
||||
|
||||
if (Platform.isDebugBuild()) {
|
||||
System.out.println("Skip the rest of the tests on debug builds since diagnostic, develop, and notproduct options are available on debug builds.");
|
||||
|
103
test/hotspot/jtreg/runtime/LoadClass/TestResize.java
Normal file
103
test/hotspot/jtreg/runtime/LoadClass/TestResize.java
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8184765
|
||||
* @summary make sure the SystemDictionary gets resized when load factor is too high
|
||||
* @library /test/lib
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* java.management
|
||||
* @compile TriggerResize.java
|
||||
* @run driver TestResize
|
||||
*/
|
||||
|
||||
import java.lang.ProcessBuilder;
|
||||
import java.lang.Process;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class TestResize {
|
||||
|
||||
static double MAX_LOAD_FACTOR = 5.0; // see _resize_load_trigger in dictionary.cpp
|
||||
|
||||
static int getInt(String string) {
|
||||
int start = 0;
|
||||
for (int i = 0; i < string.length(); i++) {
|
||||
if (!Character.isDigit(string.charAt(i))) {
|
||||
start++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
int end = start;
|
||||
for (int i = end; i < string.length(); i++) {
|
||||
if (Character.isDigit(string.charAt(i))) {
|
||||
end++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Integer.parseInt(string.substring(start, end));
|
||||
}
|
||||
|
||||
static void analyzeOutputOn(ProcessBuilder pb) throws Exception {
|
||||
pb.redirectErrorStream(true);
|
||||
Process process = pb.start();
|
||||
BufferedReader rd = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line = rd.readLine();
|
||||
while (line != null) {
|
||||
if (line.startsWith("Java dictionary (")) {
|
||||
// ex. "Java dictionary (table_size=107, classes=6)"
|
||||
// ex. "Java dictionary (table_size=20201, classes=50002)"
|
||||
Scanner scanner = new Scanner(line);
|
||||
scanner.next();
|
||||
scanner.next();
|
||||
int table_size = getInt(scanner.next());
|
||||
int classes = getInt(scanner.next());
|
||||
scanner.close();
|
||||
|
||||
double loadFactor = (double)classes / (double)table_size;
|
||||
if (loadFactor > MAX_LOAD_FACTOR) {
|
||||
throw new RuntimeException("Load factor too high, expected MAX "+MAX_LOAD_FACTOR+", got "+loadFactor);
|
||||
} else {
|
||||
System.out.println("PASS table_size:"+table_size+", classes:"+classes+" OK");
|
||||
}
|
||||
}
|
||||
line = rd.readLine();
|
||||
}
|
||||
int retval = process.waitFor();
|
||||
if (retval != 0) {
|
||||
throw new RuntimeException("Error: test returned non-zero value");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-XX:+PrintSystemDictionaryAtExit",
|
||||
"TriggerResize",
|
||||
"50000");
|
||||
analyzeOutputOn(pb);
|
||||
}
|
||||
}
|
105
test/hotspot/jtreg/runtime/LoadClass/TriggerResize.java
Normal file
105
test/hotspot/jtreg/runtime/LoadClass/TriggerResize.java
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.lang.ClassLoader;
|
||||
|
||||
public class TriggerResize extends ClassLoader
|
||||
{
|
||||
static private int[] DATA = // bytes for "class TestCase00000 {}"
|
||||
{
|
||||
-54, -2, -70, -66, 0, 0, 0, 52, 0, 13, // 0
|
||||
10, 0, 3, 0, 10, 7, 0, 11, 7, 0, // 10
|
||||
12, 1, 0, 6, 60, 105, 110, 105, 116, 62, // 20
|
||||
1, 0, 3, 40, 41, 86, 1, 0, 4, 67, // 30
|
||||
111, 100, 101, 1, 0, 15, 76, 105, 110, 101, // 40
|
||||
78, 117, 109, 98, 101, 114, 84, 97, 98, 108, // 50
|
||||
101, 1, 0, 10, 83, 111, 117, 114, 99, 101, // 60
|
||||
70, 105, 108, 101, 1, 0, 18, 84, 101, 115, // 70
|
||||
116, 67, 97, 115, 101, 48, 48, 48, 48, 48, // 80
|
||||
46, 106, 97, 118, 97, 12, 0, 4, 0, 5, // 90
|
||||
1, 0, 13, 84, 101, 115, 116, 67, 97, 115, // 100
|
||||
101, 48, 48, 48, 48, 48, 1, 0, 16, 106, // 110
|
||||
97, 118, 97, 47, 108, 97, 110, 103, 47, 79, // 120
|
||||
98, 106, 101, 99, 116, 0, 32, 0, 2, 0, // 130
|
||||
3, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 140
|
||||
4, 0, 5, 0, 1, 0, 6, 0, 0, 0, // 150
|
||||
29, 0, 1, 0, 1, 0, 0, 0, 5, 42, // 160
|
||||
-73, 0, 1, -79, 0, 0, 0, 1, 0, 7, // 170
|
||||
0, 0, 0, 6, 0, 1, 0, 0, 0, 1, // 180
|
||||
0, 1, 0, 8, 0, 0, 0, 2, 0, 9 // 190
|
||||
};
|
||||
|
||||
static private int INDEX1 = 85;
|
||||
static private int INDEX2 = 111;
|
||||
static private int BASE = 48;
|
||||
|
||||
public TriggerResize()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public void load(int index)
|
||||
{
|
||||
byte[] bytes = new byte[TriggerResize.DATA.length];
|
||||
for (int i=0; i<bytes.length; i++)
|
||||
{
|
||||
bytes[i] = (byte)TriggerResize.DATA[i];
|
||||
}
|
||||
|
||||
// replace id "00000" in TestCase00000 to generate new class on the fly
|
||||
{
|
||||
int byte1 = index % 10;
|
||||
int byte2 = index / 10 % 10;
|
||||
int byte3 = index / 100 % 10;
|
||||
int byte4 = index / 1000 % 10;
|
||||
int byte5 = index / 10000 % 10;
|
||||
|
||||
bytes[INDEX1+0] = bytes[INDEX2+0] = (byte)(BASE+byte5);
|
||||
bytes[INDEX1+1] = bytes[INDEX2+1] = (byte)(BASE+byte4);
|
||||
bytes[INDEX1+2] = bytes[INDEX2+2] = (byte)(BASE+byte3);
|
||||
bytes[INDEX1+3] = bytes[INDEX2+3] = (byte)(BASE+byte2);
|
||||
bytes[INDEX1+4] = bytes[INDEX2+4] = (byte)(BASE+byte1);
|
||||
}
|
||||
|
||||
Class generatedClass = defineClass(bytes, 0, bytes.length);
|
||||
resolveClass(generatedClass);
|
||||
}
|
||||
|
||||
public static void main(String args[]) throws Exception
|
||||
{
|
||||
int count = 0;
|
||||
if (args.length >= 1) {
|
||||
Integer i = new Integer(args[0]);
|
||||
count = i.intValue();
|
||||
}
|
||||
|
||||
TriggerResize test = new TriggerResize();
|
||||
for (int i = 0; i <= count; i++)
|
||||
{
|
||||
test.load(i);
|
||||
}
|
||||
|
||||
// trigger safepoint to resize the SystemDictionary if needed
|
||||
System.gc();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user