--- old/src/hotspot/share/classfile/classLoaderData.cpp 2018-08-03 16:41:09.000000000 -0500 +++ new/src/hotspot/share/classfile/classLoaderData.cpp 2018-08-03 16:41:09.000000000 -0500 @@ -1408,6 +1408,7 @@ } data = data->next(); } + SymbolTable::do_check_concurrent_work(); JFR_ONLY(post_class_unload_events();) } --- old/src/hotspot/share/classfile/compactHashtable.hpp 2018-08-03 16:41:10.000000000 -0500 +++ new/src/hotspot/share/classfile/compactHashtable.hpp 2018-08-03 16:41:10.000000000 -0500 @@ -231,6 +231,10 @@ // For reading from/writing to the CDS archive void serialize(SerializeClosure* soc); + + inline bool empty() { + return (_entry_count == 0); + } }; template class CompactHashtable : public SimpleCompactHashtable { --- old/src/hotspot/share/classfile/stringTable.cpp 2018-08-03 16:41:10.000000000 -0500 +++ new/src/hotspot/share/classfile/stringTable.cpp 2018-08-03 16:41:10.000000000 -0500 @@ -64,9 +64,9 @@ // -------------------------------------------------------------------------- StringTable* StringTable::_the_table = NULL; -bool StringTable::_shared_string_mapped = false; CompactHashtable StringTable::_shared_table; -bool StringTable::_alt_hash = false; +volatile bool StringTable::_shared_string_mapped = false; +volatile bool StringTable::_alt_hash = false; static juint murmur_seed = 0; @@ -176,18 +176,18 @@ } }; -static size_t ceil_pow_2(uintx val) { +static size_t log2_ceil(uintx val) { size_t ret; for (ret = 1; ((size_t)1 << ret) < val; ++ret); return ret; } StringTable::StringTable() : _local_table(NULL), _current_size(0), _has_work(0), - _needs_rehashing(false), _weak_handles(NULL), _items(0), _uncleaned_items(0) { + _needs_rehashing(false), _weak_handles(NULL), _items_count(0), _uncleaned_items_count(0) { _weak_handles = new OopStorage("StringTable weak", StringTableWeakAlloc_lock, StringTableWeakActive_lock); - size_t start_size_log_2 = ceil_pow_2(StringTableSize); + size_t start_size_log_2 = log2_ceil(StringTableSize); _current_size = ((size_t)1) << start_size_log_2; log_trace(stringtable)("Start size: " SIZE_FORMAT " (" SIZE_FORMAT ")", _current_size, start_size_log_2); @@ -195,27 +195,27 @@ } size_t StringTable::item_added() { - return Atomic::add((size_t)1, &(the_table()->_items)); + return Atomic::add((size_t)1, &(the_table()->_items_count)); } -size_t StringTable::add_items_to_clean(size_t ndead) { - size_t total = Atomic::add((size_t)ndead, &(the_table()->_uncleaned_items)); +size_t StringTable::add_items_count_to_clean(size_t ndead) { + size_t total = Atomic::add((size_t)ndead, &(the_table()->_uncleaned_items_count)); log_trace(stringtable)( "Uncleaned items:" SIZE_FORMAT " added: " SIZE_FORMAT " total:" SIZE_FORMAT, - the_table()->_uncleaned_items, ndead, total); + the_table()->_uncleaned_items_count, ndead, total); return total; } void StringTable::item_removed() { - Atomic::add((size_t)-1, &(the_table()->_items)); + Atomic::add((size_t)-1, &(the_table()->_items_count)); } double StringTable::get_load_factor() { - return (_items*1.0)/_current_size; + return (double)_items_count/_current_size; } double StringTable::get_dead_factor() { - return (_uncleaned_items*1.0)/_current_size; + return (double)_uncleaned_items_count/_current_size; } size_t StringTable::table_size(Thread* thread) { @@ -406,7 +406,7 @@ // This is the serial case without ParState. // Just set the correct number and check for a cleaning phase. - the_table()->_uncleaned_items = stiac._count; + the_table()->_uncleaned_items_count = stiac._count; StringTable::the_table()->check_concurrent_work(); if (processed != NULL) { @@ -433,7 +433,7 @@ _par_state_string->weak_oops_do(&stiac, &dnc); // Accumulate the dead strings. - the_table()->add_items_to_clean(stiac._count); + the_table()->add_items_count_to_clean(stiac._count); *processed = (int) stiac._count_total; *removed = (int) stiac._count; @@ -843,7 +843,7 @@ assert(MetaspaceShared::is_heap_object_archiving_allowed(), "must be"); _shared_table.reset(); - int num_buckets = the_table()->_items / SharedSymbolTableBucketSize; + int num_buckets = the_table()->_items_count / SharedSymbolTableBucketSize; // calculation of num_buckets can result in zero buckets, we need at least one CompactStringTableWriter writer(num_buckets > 1 ? num_buckets : 1, &MetaspaceShared::stats()->string); --- old/src/hotspot/share/classfile/stringTable.hpp 2018-08-03 16:41:11.000000000 -0500 +++ new/src/hotspot/share/classfile/stringTable.hpp 2018-08-03 16:41:11.000000000 -0500 @@ -58,21 +58,22 @@ static StringTable* _the_table; // Shared string table static CompactHashtable _shared_table; - static bool _shared_string_mapped; - static bool _alt_hash; + static volatile bool _shared_string_mapped; + static volatile bool _alt_hash; + private: - // Set if one bucket is out of balance due to hash algorithm deficiency StringTableHash* _local_table; size_t _current_size; volatile bool _has_work; + // Set if one bucket is out of balance due to hash algorithm deficiency volatile bool _needs_rehashing; OopStorage* _weak_handles; - volatile size_t _items; + volatile size_t _items_count; DEFINE_PAD_MINUS_SIZE(1, DEFAULT_CACHE_LINE_SIZE, sizeof(volatile size_t)); - volatile size_t _uncleaned_items; + volatile size_t _uncleaned_items_count; DEFINE_PAD_MINUS_SIZE(2, DEFAULT_CACHE_LINE_SIZE, sizeof(volatile size_t)); double get_load_factor(); @@ -83,7 +84,7 @@ static size_t item_added(); static void item_removed(); - size_t add_items_to_clean(size_t ndead); + size_t add_items_count_to_clean(size_t ndead); StringTable(); @@ -116,7 +117,7 @@ // Must be called before a parallel walk where strings might die. static void reset_dead_counter() { - the_table()->_uncleaned_items = 0; + the_table()->_uncleaned_items_count = 0; } // After the parallel walk this method must be called to trigger // cleaning. Note it might trigger a resize instead. @@ -127,7 +128,7 @@ // If GC uses ParState directly it should add the number of cleared // strings to this method. static void inc_dead_counter(size_t ndead) { - the_table()->add_items_to_clean(ndead); + the_table()->add_items_count_to_clean(ndead); } // Delete pointers to otherwise-unreachable objects. --- old/src/hotspot/share/classfile/symbolTable.cpp 2018-08-03 16:41:11.000000000 -0500 +++ new/src/hotspot/share/classfile/symbolTable.cpp 2018-08-03 16:41:11.000000000 -0500 @@ -27,46 +27,183 @@ #include "classfile/compactHashtable.inline.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" -#include "classfile/systemDictionary.hpp" -#include "gc/shared/collectedHeap.inline.hpp" #include "memory/allocation.inline.hpp" -#include "memory/filemap.hpp" #include "memory/metaspaceClosure.hpp" #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" #include "runtime/atomic.hpp" -#include "runtime/mutexLocker.hpp" -#include "runtime/safepointVerifiers.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/timerTrace.hpp" #include "services/diagnosticCommand.hpp" -#include "utilities/hashtable.inline.hpp" +#include "utilities/concurrentHashTable.inline.hpp" +#include "utilities/concurrentHashTableTasks.inline.hpp" -// -------------------------------------------------------------------------- -// the number of buckets a thread claims -const int ClaimChunkSize = 32; +// We used to not resize at all, so let's be conservative +// and not set it too short before we decide to resize, +// to match previous startup behavior +#define PREF_AVG_LIST_LEN 8 +// 2^17 (131,072) is max size, which is about 6.5 times as large +// as the previous table size (used to be 20,011), +// which never resized +#define END_SIZE 17 +// If a chain gets to 100 something might be wrong +#define REHASH_LEN 100 +// We only get a chance to check whether we need +// to clean infrequently (on class unloading), +// so if we have even one dead entry then mark table for cleaning +#define CLEAN_DEAD_HIGH_WATER_MARK 0.0 + +#define ON_STACK_BUFFER_LENGTH 128 +// -------------------------------------------------------------------------- SymbolTable* SymbolTable::_the_table = NULL; +CompactHashtable SymbolTable::_shared_table; +volatile bool SymbolTable::_alt_hash = false; +volatile bool SymbolTable::_lookup_shared_first = false; // Static arena for symbols that are not deallocated Arena* SymbolTable::_arena = NULL; -bool SymbolTable::_needs_rehashing = false; -bool SymbolTable::_lookup_shared_first = false; -CompactHashtable SymbolTable::_shared_table; +static juint murmur_seed = 0; + +static inline void _log_trace_symboltable_helper(Symbol* sym, const char* msg) { +#ifndef PRODUCT + if (log_is_enabled(Trace, symboltable)) { + if (sym->as_quoted_ascii() == NULL) { + log_trace(symboltable)("%s [%s]", msg, "NULL"); + } else { + log_trace(symboltable)("%s [%s]", msg, sym->as_quoted_ascii()); + } + } +#endif // PRODUCT +} + +// Pick hashing algorithm. +static uintx hash_symbol(const char* s, int len, bool useAlt) { + return useAlt ? + AltHashing::murmur3_32(murmur_seed, (const jbyte*)s, len) : + java_lang_String::hash_code((const jbyte*)s, len); +} -Symbol* SymbolTable::allocate_symbol(const u1* name, int len, bool c_heap, TRAPS) { +static uintx hash_shared_symbol(const char* s, int len) { + return java_lang_String::hash_code((const jbyte*)s, len); +} + +class SymbolTableConfig : public SymbolTableHash::BaseConfig { +private: +public: + static uintx get_hash(Symbol* const& value, bool* is_dead) { + *is_dead = (value->refcount() == 0); + if (*is_dead) { + return 0; + } else { + return hash_symbol((const char*)value->bytes(), value->utf8_length(), SymbolTable::_alt_hash); + } + } + // We use default allocation/deallocation but counted + static void* allocate_node(size_t size, Symbol* const& value) { + SymbolTable::item_added(); + return SymbolTableHash::BaseConfig::allocate_node(size, value); + } + static void free_node(void* memory, Symbol* const& value) { + // We get here either because #1 some threads lost a race + // to insert a newly created Symbol, or #2 we are freeing + // a symbol during normal cleanup deletion. + // If #1, then the symbol can be a permanent (refcount==PERM_REFCOUNT), + // or regular newly created one but with refcount==0 (see SymbolTableCreateEntry) + // If #2, then the symbol must have refcount==0 + assert((value->refcount() == PERM_REFCOUNT) || (value->refcount() == 0), + "refcount %d", value->refcount()); + SymbolTable::delete_symbol(value); + SymbolTableHash::BaseConfig::free_node(memory, value); + SymbolTable::item_removed(); + } +}; + +static size_t log2_ceil(uintx value) { + size_t ret; + for (ret = 1; ((size_t)1 << ret) < value; ++ret); + return ret; +} + +SymbolTable::SymbolTable() : _local_table(NULL), _current_size(0), _has_work(0), + _needs_rehashing(false), _items_count(0), _uncleaned_items_count(0), + _symbols_removed(0), _symbols_counted(0) { + + size_t start_size_log_2 = log2_ceil(SymbolTableSize); + _current_size = ((size_t)1) << start_size_log_2; + log_trace(symboltable)("Start size: " SIZE_FORMAT " (" SIZE_FORMAT ")", + _current_size, start_size_log_2); + _local_table = new SymbolTableHash(start_size_log_2, END_SIZE, REHASH_LEN); +} + +void SymbolTable::delete_symbol(Symbol* sym) { + if (sym->refcount() == PERM_REFCOUNT) { + MutexLocker ml(SymbolTable_lock); // Protect arena + // Deleting permanent symbol should not occur very often (insert race condition), + // so log it. + _log_trace_symboltable_helper(sym, "Freeing permanent symbol"); + if (!arena()->Afree(sym, sym->size())) { + _log_trace_symboltable_helper(sym, "Leaked permanent symbol"); + } + } else { + delete sym; + } +} + +void SymbolTable::item_added() { + Atomic::inc(&(SymbolTable::the_table()->_items_count)); +} + +void SymbolTable::set_item_clean_count(size_t ncl) { + Atomic::store(ncl, &(SymbolTable::the_table()->_uncleaned_items_count)); + log_trace(symboltable)("Set uncleaned items:" SIZE_FORMAT, SymbolTable::the_table()->_uncleaned_items_count); +} + +void SymbolTable::mark_item_clean_count() { + if (Atomic::cmpxchg((size_t)1, &(SymbolTable::the_table()->_uncleaned_items_count), (size_t)0) == 0) { // only mark if unset + log_trace(symboltable)("Marked uncleaned items:" SIZE_FORMAT, SymbolTable::the_table()->_uncleaned_items_count); + } +} + +void SymbolTable::item_removed() { + Atomic::inc(&(SymbolTable::the_table()->_symbols_removed)); + Atomic::dec(&(SymbolTable::the_table()->_items_count)); +} + +double SymbolTable::get_load_factor() { + return (double)_items_count/_current_size; +} + +double SymbolTable::get_dead_factor() { + return (double)_uncleaned_items_count/_current_size; +} + +size_t SymbolTable::table_size(Thread* thread) { + return ((size_t)(1)) << _local_table->get_size_log2(thread != NULL ? thread + : Thread::current()); +} + +void SymbolTable::trigger_concurrent_work() { + MutexLockerEx ml(Service_lock, Mutex::_no_safepoint_check_flag); + SymbolTable::the_table()->_has_work = true; + Service_lock->notify_all(); +} + +Symbol* SymbolTable::allocate_symbol(const char* name, int len, bool c_heap, TRAPS) { assert (len <= Symbol::max_length(), "should be checked by caller"); Symbol* sym; - if (DumpSharedSpaces) { c_heap = false; } if (c_heap) { // refcount starts as 1 - sym = new (len, THREAD) Symbol(name, len, 1); + sym = new (len, THREAD) Symbol((const u1*)name, len, 1); assert(sym != NULL, "new should call vm_exit_out_of_memory if C_HEAP is exhausted"); } else { // Allocate to global arena - sym = new (len, arena(), THREAD) Symbol(name, len, PERM_REFCOUNT); + MutexLocker ml(SymbolTable_lock); // Protect arena + sym = new (len, arena(), THREAD) Symbol((const u1*)name, len, PERM_REFCOUNT); } return sym; } @@ -80,314 +217,176 @@ } } +class SymbolsDo : StackObj { + SymbolClosure *_cl; +public: + SymbolsDo(SymbolClosure *cl) : _cl(cl) {} + bool operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + _cl->do_symbol(value); + return true; + }; +}; + // Call function for all symbols in the symbol table. void SymbolTable::symbols_do(SymbolClosure *cl) { // all symbols from shared table _shared_table.symbols_do(cl); // all symbols from the dynamic table - const int n = the_table()->table_size(); - for (int i = 0; i < n; i++) { - for (HashtableEntry* p = the_table()->bucket(i); - p != NULL; - p = p->next()) { - cl->do_symbol(p->literal_addr()); - } + SymbolsDo sd(cl); + if (!SymbolTable::the_table()->_local_table->try_scan(Thread::current(), sd)) { + log_info(stringtable)("symbols_do unavailable at this moment"); } } +class MetaspacePointersDo : StackObj { + MetaspaceClosure *_it; +public: + MetaspacePointersDo(MetaspaceClosure *it) : _it(it) {} + bool operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + _it->push(value); + return true; + }; +}; + void SymbolTable::metaspace_pointers_do(MetaspaceClosure* it) { assert(DumpSharedSpaces, "called only during dump time"); - const int n = the_table()->table_size(); - for (int i = 0; i < n; i++) { - for (HashtableEntry* p = the_table()->bucket(i); - p != NULL; - p = p->next()) { - it->push(p->literal_addr()); - } - } -} - -int SymbolTable::_symbols_removed = 0; -int SymbolTable::_symbols_counted = 0; -volatile int SymbolTable::_parallel_claimed_idx = 0; - -void SymbolTable::buckets_unlink(int start_idx, int end_idx, BucketUnlinkContext* context) { - for (int i = start_idx; i < end_idx; ++i) { - HashtableEntry** p = the_table()->bucket_addr(i); - HashtableEntry* entry = the_table()->bucket(i); - while (entry != NULL) { - // Shared entries are normally at the end of the bucket and if we run into - // a shared entry, then there is nothing more to remove. However, if we - // have rehashed the table, then the shared entries are no longer at the - // end of the bucket. - if (entry->is_shared() && !use_alternate_hashcode()) { - break; - } - Symbol* s = entry->literal(); - context->_num_processed++; - assert(s != NULL, "just checking"); - // If reference count is zero, remove. - if (s->refcount() == 0) { - assert(!entry->is_shared(), "shared entries should be kept live"); - delete s; - *p = entry->next(); - context->free_entry(entry); - } else { - p = entry->next_addr(); - } - // get next entry - entry = (HashtableEntry*)HashtableEntry::make_ptr(*p); - } - } -} - -// Remove unreferenced symbols from the symbol table -// This is done late during GC. -void SymbolTable::unlink(int* processed, int* removed) { - BucketUnlinkContext context; - buckets_unlink(0, the_table()->table_size(), &context); - _the_table->bulk_free_entries(&context); - *processed = context._num_processed; - *removed = context._num_removed; - - _symbols_removed = context._num_removed; - _symbols_counted = context._num_processed; + MetaspacePointersDo mpd(it); + SymbolTable::the_table()->_local_table->do_scan(Thread::current(), mpd); } -void SymbolTable::possibly_parallel_unlink(int* processed, int* removed) { - const int limit = the_table()->table_size(); - - BucketUnlinkContext context; - for (;;) { - // Grab next set of buckets to scan - int start_idx = Atomic::add(ClaimChunkSize, &_parallel_claimed_idx) - ClaimChunkSize; - if (start_idx >= limit) { - // End of table - break; - } - - int end_idx = MIN2(limit, start_idx + ClaimChunkSize); - buckets_unlink(start_idx, end_idx, &context); - } - - _the_table->bulk_free_entries(&context); - *processed = context._num_processed; - *removed = context._num_removed; - - Atomic::add(context._num_processed, &_symbols_counted); - Atomic::add(context._num_removed, &_symbols_removed); -} - -// Create a new table and using alternate hash code, populate the new table -// with the existing strings. Set flag to use the alternate hash code afterwards. -void SymbolTable::rehash_table() { - if (DumpSharedSpaces) { - tty->print_cr("Warning: rehash_table should not be called while dumping archive"); - return; - } - - assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint"); - // This should never happen with -Xshare:dump but it might in testing mode. - if (DumpSharedSpaces) return; - - // Create a new symbol table - SymbolTable* new_table = new SymbolTable(); - - the_table()->move_to(new_table); - - // Delete the table and buckets (entries are reused in new table). - delete _the_table; - // Don't check if we need rehashing until the table gets unbalanced again. - // Then rehash with a new global seed. - _needs_rehashing = false; - _the_table = new_table; -} - -// Lookup a symbol in a bucket. - -Symbol* SymbolTable::lookup_dynamic(int index, const char* name, +Symbol* SymbolTable::lookup_dynamic(const char* name, int len, unsigned int hash) { - int count = 0; - for (HashtableEntry* e = bucket(index); e != NULL; e = e->next()) { - count++; // count all entries in this bucket, not just ones with same hash - if (e->hash() == hash) { - Symbol* sym = e->literal(); - // Skip checking already dead symbols in the bucket. - if (sym->refcount() == 0) { - count--; // Don't count this symbol towards rehashing. - } else if (sym->equals(name, len)) { - if (sym->try_increment_refcount()) { - // something is referencing this symbol now. - return sym; - } else { - count--; // don't count this symbol. - } - } - } - } - // If the bucket size is too deep check if this hash code is insufficient. - if (count >= rehash_count && !needs_rehashing()) { - _needs_rehashing = check_rehash_table(count); - } - return NULL; + Symbol* sym = SymbolTable::the_table()->do_lookup(name, len, hash); + assert((sym == NULL) || sym->refcount() != 0, "refcount must not be zero"); + return sym; } Symbol* SymbolTable::lookup_shared(const char* name, int len, unsigned int hash) { - if (use_alternate_hashcode()) { - // hash_code parameter may use alternate hashing algorithm but the shared table - // always uses the same original hash code. - hash = hash_shared_symbol(name, len); + if (!_shared_table.empty()) { + if (SymbolTable::_alt_hash) { + // hash_code parameter may use alternate hashing algorithm but the shared table + // always uses the same original hash code. + hash = hash_shared_symbol(name, len); + } + return _shared_table.lookup(name, hash, len); + } else { + return NULL; } - return _shared_table.lookup(name, hash, len); } -Symbol* SymbolTable::lookup(int index, const char* name, +Symbol* SymbolTable::lookup_common(const char* name, int len, unsigned int hash) { Symbol* sym; if (_lookup_shared_first) { sym = lookup_shared(name, len, hash); - if (sym != NULL) { - return sym; + if (sym == NULL) { + _lookup_shared_first = false; + sym = lookup_dynamic(name, len, hash); } - _lookup_shared_first = false; - return lookup_dynamic(index, name, len, hash); } else { - sym = lookup_dynamic(index, name, len, hash); - if (sym != NULL) { - return sym; - } - sym = lookup_shared(name, len, hash); - if (sym != NULL) { - _lookup_shared_first = true; + sym = lookup_dynamic(name, len, hash); + if (sym == NULL) { + sym = lookup_shared(name, len, hash); + if (sym != NULL) { + _lookup_shared_first = true; + } } - return sym; } -} - -u4 SymbolTable::encode_shared(Symbol* sym) { - assert(DumpSharedSpaces, "called only during dump time"); - uintx base_address = uintx(MetaspaceShared::shared_rs()->base()); - uintx offset = uintx(sym) - base_address; - assert(offset < 0x7fffffff, "sanity"); - return u4(offset); -} - -Symbol* SymbolTable::decode_shared(u4 offset) { - assert(!DumpSharedSpaces, "called only during runtime"); - uintx base_address = _shared_table.base_address(); - Symbol* sym = (Symbol*)(base_address + offset); - -#ifndef PRODUCT - const char* s = (const char*)sym->bytes(); - int len = sym->utf8_length(); - unsigned int hash = hash_symbol(s, len); - assert(sym == lookup_shared(s, len, hash), "must be shared symbol"); -#endif - return sym; } -// Pick hashing algorithm. -unsigned int SymbolTable::hash_symbol(const char* s, int len) { - return use_alternate_hashcode() ? - AltHashing::murmur3_32(seed(), (const jbyte*)s, len) : - java_lang_String::hash_code((const jbyte*)s, len); -} - -unsigned int SymbolTable::hash_shared_symbol(const char* s, int len) { - return java_lang_String::hash_code((const jbyte*)s, len); -} - - -// We take care not to be blocking while holding the -// SymbolTable_lock. Otherwise, the system might deadlock, since the -// symboltable is used during compilation (VM_thread) The lock free -// synchronization is simplified by the fact that we do not delete -// entries in the symbol table during normal execution (only during -// safepoints). - Symbol* SymbolTable::lookup(const char* name, int len, TRAPS) { - unsigned int hashValue = hash_symbol(name, len); - int index = the_table()->hash_to_index(hashValue); - - Symbol* s = the_table()->lookup(index, name, len, hashValue); - - // Found - if (s != NULL) return s; - - // Grab SymbolTable_lock first. - MutexLocker ml(SymbolTable_lock, THREAD); - - // Otherwise, add to symbol to table - return the_table()->basic_add(index, (u1*)name, len, hashValue, true, THREAD); + unsigned int hash = hash_symbol(name, len, SymbolTable::_alt_hash); + Symbol* sym = SymbolTable::the_table()->lookup_common(name, len, hash); + if (sym == NULL) { + sym = SymbolTable::the_table()->do_add_if_needed(name, len, hash, true, CHECK_NULL); + } + assert(sym->refcount() != 0, "lookup should have incremented the count"); + assert(sym->equals(name, len), "symbol must be properly initialized"); + return sym; } Symbol* SymbolTable::lookup(const Symbol* sym, int begin, int end, TRAPS) { - char* buffer; - int index, len; - unsigned int hashValue; - char* name; - { - debug_only(NoSafepointVerifier nsv;) - - name = (char*)sym->base() + begin; - len = end - begin; - hashValue = hash_symbol(name, len); - index = the_table()->hash_to_index(hashValue); - Symbol* s = the_table()->lookup(index, name, len, hashValue); - - // Found - if (s != NULL) return s; + assert(sym->refcount() != 0, "require a valid symbol"); + const char* name = (const char*)sym->base() + begin; + int len = end - begin; + unsigned int hash = hash_symbol(name, len, SymbolTable::_alt_hash); + Symbol* found = SymbolTable::the_table()->lookup_common(name, len, hash); + if (found == NULL) { + found = SymbolTable::the_table()->do_add_if_needed(name, len, hash, true, THREAD); + } + return found; +} + +class SymbolTableLookup : StackObj { +private: + Thread* _thread; + uintx _hash; + int _len; + const char* _str; +public: + SymbolTableLookup(Thread* thread, const char* key, int len, uintx hash) + : _thread(thread), _hash(hash), _str(key), _len(len) {} + uintx get_hash() const { + return _hash; + } + bool equals(Symbol** value, bool* is_dead) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + Symbol *sym = *value; + if (sym->equals(_str, _len)) { + if (sym->try_increment_refcount()) { + // something is referencing this symbol now. + return true; + } else { + assert(sym->refcount() == 0, "expected dead symbol"); + *is_dead = true; + return false; + } + } else { + *is_dead = (sym->refcount() == 0); + return false; + } } +}; - // Otherwise, add to symbol to table. Copy to a C string first. - char stack_buf[128]; - ResourceMark rm(THREAD); - if (len <= 128) { - buffer = stack_buf; - } else { - buffer = NEW_RESOURCE_ARRAY_IN_THREAD(THREAD, char, len); +class SymbolTableGet : public StackObj { + Symbol* _return; +public: + SymbolTableGet() : _return(NULL) { } + void operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + _return = *value; + } + Symbol* get_res_sym() { + return _return; + } +}; + +Symbol* SymbolTable::do_lookup(const char* name, int len, uintx hash) { + Thread* thread = Thread::current(); + SymbolTableLookup lookup(thread, name, len, hash); + SymbolTableGet stg; + bool rehash_warning = false; + _local_table->get(thread, lookup, stg, &rehash_warning); + if (rehash_warning) { + _needs_rehashing = true; } - for (int i=0; ibasic_add(index, (u1*)buffer, len, hashValue, true, THREAD); -} - -Symbol* SymbolTable::lookup_only(const char* name, int len, - unsigned int& hash) { - hash = hash_symbol(name, len); - int index = the_table()->hash_to_index(hash); - - Symbol* s = the_table()->lookup(index, name, len, hash); - return s; + Symbol* sym = stg.get_res_sym(); + assert((sym == NULL) || sym->refcount() != 0, "found dead symbol"); + return sym; } -// Look up the address of the literal in the SymbolTable for this Symbol* -// Do not create any new symbols -// Do not increment the reference count to keep this alive -Symbol** SymbolTable::lookup_symbol_addr(Symbol* sym){ - unsigned int hash = hash_symbol((char*)sym->bytes(), sym->utf8_length()); - int index = the_table()->hash_to_index(hash); - - for (HashtableEntry* e = the_table()->bucket(index); e != NULL; e = e->next()) { - if (e->hash() == hash) { - Symbol* literal_sym = e->literal(); - if (sym == literal_sym) { - return e->literal_addr(); - } - } - } - return NULL; +Symbol* SymbolTable::lookup_only(const char* name, int len, unsigned int& hash) { + hash = hash_symbol(name, len, SymbolTable::_alt_hash); + return SymbolTable::the_table()->lookup_common(name, len, hash); } // Suggestion: Push unicode-based lookup all the way into the hashing @@ -395,14 +394,14 @@ // an actual new Symbol* is created. Symbol* SymbolTable::lookup_unicode(const jchar* name, int utf16_length, TRAPS) { int utf8_length = UNICODE::utf8_length((jchar*) name, utf16_length); - char stack_buf[128]; + char stack_buf[ON_STACK_BUFFER_LENGTH]; if (utf8_length < (int) sizeof(stack_buf)) { char* chars = stack_buf; UNICODE::convert_to_utf8(name, utf16_length, chars); return lookup(chars, utf8_length, THREAD); } else { ResourceMark rm(THREAD); - char* chars = NEW_RESOURCE_ARRAY(char, utf8_length + 1);; + char* chars = NEW_RESOURCE_ARRAY(char, utf8_length + 1); UNICODE::convert_to_utf8(name, utf16_length, chars); return lookup(chars, utf8_length, THREAD); } @@ -411,222 +410,445 @@ Symbol* SymbolTable::lookup_only_unicode(const jchar* name, int utf16_length, unsigned int& hash) { int utf8_length = UNICODE::utf8_length((jchar*) name, utf16_length); - char stack_buf[128]; + char stack_buf[ON_STACK_BUFFER_LENGTH]; if (utf8_length < (int) sizeof(stack_buf)) { char* chars = stack_buf; UNICODE::convert_to_utf8(name, utf16_length, chars); return lookup_only(chars, utf8_length, hash); } else { ResourceMark rm; - char* chars = NEW_RESOURCE_ARRAY(char, utf8_length + 1);; + char* chars = NEW_RESOURCE_ARRAY(char, utf8_length + 1); UNICODE::convert_to_utf8(name, utf16_length, chars); return lookup_only(chars, utf8_length, hash); } } void SymbolTable::add(ClassLoaderData* loader_data, const constantPoolHandle& cp, - int names_count, - const char** names, int* lengths, int* cp_indices, - unsigned int* hashValues, TRAPS) { - // Grab SymbolTable_lock first. - MutexLocker ml(SymbolTable_lock, THREAD); - - SymbolTable* table = the_table(); - bool added = table->basic_add(loader_data, cp, names_count, names, lengths, - cp_indices, hashValues, CHECK); - if (!added) { - // do it the hard way - for (int i=0; ihash_to_index(hashValues[i]); - bool c_heap = !loader_data->is_the_null_class_loader_data(); - Symbol* sym = table->basic_add(index, (u1*)names[i], lengths[i], hashValues[i], c_heap, CHECK); - cp->symbol_at_put(cp_indices[i], sym); + int names_count, const char** names, int* lengths, + int* cp_indices, unsigned int* hashValues, TRAPS) { + bool c_heap = !loader_data->is_the_null_class_loader_data(); + for (int i = 0; i < names_count; i++) { + const char *name = names[i]; + int len = lengths[i]; + unsigned int hash = hashValues[i]; + Symbol* sym = SymbolTable::the_table()->lookup_common(name, len, hash); + if (sym == NULL) { + sym = SymbolTable::the_table()->do_add_if_needed(name, len, hash, c_heap, CHECK); + } + assert(sym->refcount() != 0, "lookup should have incremented the count"); + cp->symbol_at_put(cp_indices[i], sym); + } +} + +class SymbolTableCreateEntry : public StackObj { +private: + Thread* _thread; + const char* _name; + int _len; + bool _heap; + Symbol* _return; + Symbol* _created; + + void assert_for_name(Symbol* sym, const char* where) const { +#ifdef ASSERT + assert(sym->utf8_length() == _len, "%s [%d,%d]", where, sym->utf8_length(), _len); + for (int i = 0; i < _len; i++) { + assert(sym->byte_at(i) == _name[i], + "%s [%d,%d,%d]", where, i, sym->byte_at(i), _name[i]); + } +#endif + } + +public: + SymbolTableCreateEntry(Thread* thread, const char* name, int len, bool heap) + : _thread(thread), _name(name) , _len(len), _heap(heap), _return(NULL) , _created(NULL) { + assert(_name != NULL, "expected valid name"); + } + Symbol* operator()() { + _created = SymbolTable::the_table()->allocate_symbol(_name, _len, _heap, _thread); + assert(_created != NULL, "expected created symbol"); + assert_for_name(_created, "operator()()"); + assert(_created->equals(_name, _len), + "symbol must be properly initialized [%p,%d,%d]", _name, _len, (int)_heap); + return _created; + } + void operator()(bool inserted, Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + if (!inserted && (_created != NULL)) { + // We created our symbol, but someone else inserted + // theirs first, so ours will be destroyed. + // Since symbols are created with refcount of 1, + // we must decrement it here to 0 to delete, + // unless it's a permanent one. + if (_created->refcount() != PERM_REFCOUNT) { + assert(_created->refcount() == 1, "expected newly created symbol"); + _created->decrement_refcount(); + assert(_created->refcount() == 0, "expected dead symbol"); + } } + _return = *value; + assert_for_name(_return, "operator()"); + } + Symbol* get_new_sym() const { + assert_for_name(_return, "get_new_sym"); + return _return; + } +}; + +Symbol* SymbolTable::do_add_if_needed(const char* name, int len, uintx hash, bool heap, TRAPS) { + SymbolTableLookup lookup(THREAD, name, len, hash); + SymbolTableCreateEntry stce(THREAD, name, len, heap); + bool rehash_warning = false; + bool clean_hint = false; + _local_table->get_insert_lazy(THREAD, lookup, stce, stce, &rehash_warning, &clean_hint); + if (rehash_warning) { + _needs_rehashing = true; + } + if (clean_hint) { + // we just found out that there is a dead item, + // which we were unable to clean right now, + // but we have no way of telling whether it's + // been previously counted or not, so mark + // it only if no other items were found yet + mark_item_clean_count(); + check_concurrent_work(); } + Symbol* sym = stce.get_new_sym(); + assert(sym->refcount() != 0, "zero is invalid"); + return sym; } Symbol* SymbolTable::new_permanent_symbol(const char* name, TRAPS) { - unsigned int hash; - Symbol* result = SymbolTable::lookup_only((char*)name, (int)strlen(name), hash); - if (result != NULL) { - return result; + unsigned int hash = 0; + int len = (int)strlen(name); + Symbol* sym = SymbolTable::lookup_only(name, len, hash); + if (sym == NULL) { + sym = SymbolTable::the_table()->do_add_if_needed(name, len, hash, false, CHECK_NULL); + } + if (sym->refcount() != PERM_REFCOUNT) { + sym->increment_refcount(); + _log_trace_symboltable_helper(sym, "Asked for a permanent symbol, but got a regular one"); } - // Grab SymbolTable_lock first. - MutexLocker ml(SymbolTable_lock, THREAD); - - SymbolTable* table = the_table(); - int index = table->hash_to_index(hash); - return table->basic_add(index, (u1*)name, (int)strlen(name), hash, false, THREAD); + return sym; } -Symbol* SymbolTable::basic_add(int index_arg, u1 *name, int len, - unsigned int hashValue_arg, bool c_heap, TRAPS) { - assert(!Universe::heap()->is_in_reserved(name), - "proposed name of symbol must be stable"); +struct SizeFunc : StackObj { + size_t operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + return (*value)->size() * HeapWordSize; + }; +}; + +void SymbolTable::print_table_statistics(outputStream* st, + const char* table_name) { + SizeFunc sz; + _local_table->statistics_to(Thread::current(), sz, st, table_name); +} + +// Verification +class VerifySymbols : StackObj { +public: + bool operator()(Symbol** value) { + guarantee(value != NULL, "expected valid value"); + guarantee(*value != NULL, "value should point to a symbol"); + Symbol* sym = *value; + guarantee(sym->equals((const char*)sym->bytes(), sym->utf8_length()), + "symbol must be internally consistent"); + return true; + }; +}; - // Don't allow symbols to be created which cannot fit in a Symbol*. - if (len > Symbol::max_length()) { - THROW_MSG_0(vmSymbols::java_lang_InternalError(), - "name is too long to represent"); +void SymbolTable::verify() { + Thread* thr = Thread::current(); + VerifySymbols vs; + if (!SymbolTable::the_table()->_local_table->try_scan(thr, vs)) { + log_info(stringtable)("verify unavailable at this moment"); } +} - // Cannot hit a safepoint in this function because the "this" pointer can move. - NoSafepointVerifier nsv; +// Dumping +class DumpSymbol : StackObj { + Thread* _thr; + outputStream* _st; +public: + DumpSymbol(Thread* thr, outputStream* st) : _thr(thr), _st(st) {} + bool operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + Symbol* sym = *value; + const char* utf8_string = (const char*)sym->bytes(); + int utf8_length = sym->utf8_length(); + _st->print("%d %d: ", utf8_length, sym->refcount()); + HashtableTextDump::put_utf8(_st, utf8_string, utf8_length); + _st->cr(); + return true; + }; +}; - // Check if the symbol table has been rehashed, if so, need to recalculate - // the hash value and index. - unsigned int hashValue; - int index; - if (use_alternate_hashcode()) { - hashValue = hash_symbol((const char*)name, len); - index = hash_to_index(hashValue); +void SymbolTable::dump(outputStream* st, bool verbose) { + if (!verbose) { + SymbolTable::the_table()->print_table_statistics(st, "SymbolTable"); } else { - hashValue = hashValue_arg; - index = index_arg; + Thread* thr = Thread::current(); + ResourceMark rm(thr); + st->print_cr("VERSION: 1.1"); + DumpSymbol ds(thr, st); + if (!SymbolTable::the_table()->_local_table->try_scan(thr, ds)) { + log_info(symboltable)("dump unavailable at this moment"); + } } +} - // Since look-up was done lock-free, we need to check if another - // thread beat us in the race to insert the symbol. - Symbol* test = lookup(index, (char*)name, len, hashValue); - if (test != NULL) { - // A race occurred and another thread introduced the symbol. - assert(test->refcount() != 0, "lookup should have incremented the count"); - return test; - } +#if INCLUDE_CDS +struct CopyToArchive : StackObj { + CompactSymbolTableWriter* _writer; + CopyToArchive(CompactSymbolTableWriter* writer) : _writer(writer) {} + bool operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + Symbol* sym = *value; + unsigned int fixed_hash = hash_shared_symbol((const char*)sym->bytes(), sym->utf8_length()); + if (fixed_hash == 0) { + return true; + } + assert(fixed_hash == hash_symbol((const char*)sym->bytes(), sym->utf8_length(), false), + "must not rehash during dumping"); + + // add to the compact table + _writer->add(fixed_hash, sym); + + return true; + } +}; + +void SymbolTable::copy_shared_symbol_table(CompactSymbolTableWriter* writer) { + CopyToArchive copy(writer); + SymbolTable::the_table()->_local_table->do_scan(Thread::current(), copy); +} - // Create a new symbol. - Symbol* sym = allocate_symbol(name, len, c_heap, CHECK_NULL); - assert(sym->equals((char*)name, len), "symbol must be properly initialized"); +void SymbolTable::write_to_archive() { + _shared_table.reset(); - HashtableEntry* entry = new_entry(hashValue, sym); - add_entry(index, entry); - return sym; + int num_buckets = (SymbolTable::the_table()->_items_count / SharedSymbolTableBucketSize); + // calculation of num_buckets can result in zero buckets, we need at least one + CompactSymbolTableWriter writer(num_buckets > 1 ? num_buckets : 1, + &MetaspaceShared::stats()->symbol); + copy_shared_symbol_table(&writer); + writer.dump(&_shared_table); + + // Verify table is correct + Symbol* sym = vmSymbols::java_lang_Object(); + const char* name = (const char*)sym->bytes(); + int len = sym->utf8_length(); + unsigned int hash = hash_symbol(name, len, SymbolTable::_alt_hash); + assert(sym == _shared_table.lookup(name, hash, len), "sanity"); } -// This version of basic_add adds symbols in batch from the constant pool -// parsing. -bool SymbolTable::basic_add(ClassLoaderData* loader_data, const constantPoolHandle& cp, - int names_count, - const char** names, int* lengths, - int* cp_indices, unsigned int* hashValues, - TRAPS) { - - // Check symbol names are not too long. If any are too long, don't add any. - for (int i = 0; i< names_count; i++) { - if (lengths[i] > Symbol::max_length()) { - THROW_MSG_0(vmSymbols::java_lang_InternalError(), - "name is too long to represent"); - } +void SymbolTable::serialize(SerializeClosure* soc) { + _shared_table.set_type(CompactHashtable::_symbol_table); + _shared_table.serialize(soc); + + if (soc->writing()) { + // Sanity. Make sure we don't use the shared table at dump time + _shared_table.reset(); } +} +#endif //INCLUDE_CDS - // Cannot hit a safepoint in this function because the "this" pointer can move. - NoSafepointVerifier nsv; - - for (int i=0; isymbol_at_put(cp_indices[i], test); - assert(test->refcount() != 0, "lookup should have incremented the count"); - } else { - // Create a new symbol. The null class loader is never unloaded so these - // are allocated specially in a permanent arena. - bool c_heap = !loader_data->is_the_null_class_loader_data(); - Symbol* sym = allocate_symbol((const u1*)names[i], lengths[i], c_heap, CHECK_(false)); - assert(sym->equals(names[i], lengths[i]), "symbol must be properly initialized"); // why wouldn't it be??? - HashtableEntry* entry = new_entry(hashValue, sym); - add_entry(index, entry); - cp->symbol_at_put(cp_indices[i], sym); + } + gt.done(jt); + _current_size = table_size(jt); + log_debug(symboltable)("Grown to size:" SIZE_FORMAT, _current_size); +} + +struct SymbolTableDoDelete : StackObj { + int _deleted; + SymbolTableDoDelete() : _deleted(0) {} + void operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + Symbol *sym = *value; + assert(sym->refcount() == 0, "refcount"); + _deleted++; + } +}; + +struct SymbolTableDeleteCheck : StackObj { + int _processed; + SymbolTableDeleteCheck() : _processed(0) {} + bool operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + _processed++; + Symbol *sym = *value; + return (sym->refcount() == 0); + } +}; + +void SymbolTable::clean_dead_entries(JavaThread* jt) { + SymbolTableHash::BulkDeleteTask bdt(_local_table); + if (!bdt.prepare(jt)) { + return; + } + + SymbolTableDeleteCheck stdc; + SymbolTableDoDelete stdd; + { + TraceTime timer("Clean", TRACETIME_LOG(Debug, symboltable, perf)); + while (bdt.do_task(jt, stdc, stdd)) { + bdt.pause(jt); + { + ThreadBlockInVM tbivm(jt); + } + bdt.cont(jt); } + SymbolTable::the_table()->set_item_clean_count(0); + bdt.done(jt); } - return true; + + Atomic::add((size_t)stdc._processed, &_symbols_counted); + + log_debug(symboltable)("Cleaned " INT32_FORMAT " of " INT32_FORMAT, + stdd._deleted, stdc._processed); } +void SymbolTable::check_concurrent_work() { + if (_has_work) { + return; + } + double load_factor = SymbolTable::get_load_factor(); + double dead_factor = SymbolTable::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(symboltable)("Concurrent work triggered, live factor:%f dead factor:%f", + load_factor, dead_factor); + trigger_concurrent_work(); + } +} -void SymbolTable::verify() { - for (int i = 0; i < the_table()->table_size(); ++i) { - HashtableEntry* p = the_table()->bucket(i); - for ( ; p != NULL; p = p->next()) { - Symbol* s = (Symbol*)(p->literal()); - guarantee(s != NULL, "symbol is NULL"); - unsigned int h = hash_symbol((char*)s->bytes(), s->utf8_length()); - guarantee(p->hash() == h, "broken hash in symbol table entry"); - guarantee(the_table()->hash_to_index(h) == i, - "wrong index in symbol table"); - } +void SymbolTable::concurrent_work(JavaThread* jt) { + double load_factor = get_load_factor(); + log_debug(symboltable, perf)("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); } + _has_work = false; } -void SymbolTable::dump(outputStream* st, bool verbose) { - if (!verbose) { - the_table()->print_table_statistics(st, "SymbolTable"); +class CountDead : StackObj { + int _count; +public: + CountDead() : _count(0) {} + bool operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + Symbol* sym = *value; + if (sym->refcount() == 0) { + _count++; + } + return true; + }; + int get_dead_count() { + return _count; + } +}; + +void SymbolTable::do_check_concurrent_work() { + CountDead counter; + if (!SymbolTable::the_table()->_local_table->try_scan(Thread::current(), counter)) { + log_info(symboltable)("count dead unavailable at this moment"); } else { - st->print_cr("VERSION: 1.0"); - for (int i = 0; i < the_table()->table_size(); ++i) { - HashtableEntry* p = the_table()->bucket(i); - for ( ; p != NULL; p = p->next()) { - Symbol* s = (Symbol*)(p->literal()); - const char* utf8_string = (const char*)s->bytes(); - int utf8_length = s->utf8_length(); - st->print("%d %d: ", utf8_length, s->refcount()); - HashtableTextDump::put_utf8(st, utf8_string, utf8_length); - st->cr(); - } - } + SymbolTable::the_table()->set_item_clean_count(counter.get_dead_count()); + SymbolTable::the_table()->check_concurrent_work(); } } -void SymbolTable::write_to_archive() { -#if INCLUDE_CDS - _shared_table.reset(); +void SymbolTable::do_concurrent_work(JavaThread* jt) { + SymbolTable::the_table()->concurrent_work(jt); +} - int num_buckets = the_table()->number_of_entries() / - SharedSymbolTableBucketSize; - CompactSymbolTableWriter writer(num_buckets, - &MetaspaceShared::stats()->symbol); - for (int i = 0; i < the_table()->table_size(); ++i) { - HashtableEntry* p = the_table()->bucket(i); - for ( ; p != NULL; p = p->next()) { - Symbol* s = (Symbol*)(p->literal()); - unsigned int fixed_hash = hash_shared_symbol((char*)s->bytes(), s->utf8_length()); - assert(fixed_hash == p->hash(), "must not rehash during dumping"); - writer.add(fixed_hash, s); - } - } +// Rehash +bool SymbolTable::do_rehash() { + if (!_local_table->is_safepoint_safe()) { + return false; + } - writer.dump(&_shared_table); + // We use max size + SymbolTableHash* new_table = new SymbolTableHash(END_SIZE, END_SIZE, REHASH_LEN); + // Use alt hash from now on + _alt_hash = true; + if (!_local_table->try_move_nodes_to(Thread::current(), new_table)) { + _alt_hash = false; + delete new_table; + return false; + } - // Verify table is correct - Symbol* sym = vmSymbols::java_lang_Object(); - const char* name = (const char*)sym->bytes(); - int len = sym->utf8_length(); - unsigned int hash = hash_symbol(name, len); - assert(sym == _shared_table.lookup(name, hash, len), "sanity"); -#endif + // free old table + delete _local_table; + _local_table = new_table; + + return true; } -void SymbolTable::serialize(SerializeClosure* soc) { -#if INCLUDE_CDS - _shared_table.set_type(CompactHashtable::_symbol_table); - _shared_table.serialize(soc); +void SymbolTable::try_rehash_table() { + static bool rehashed = false; + log_debug(symboltable)("Table imbalanced, rehashing called."); + + // Grow instead of rehash. + if (get_load_factor() > PREF_AVG_LIST_LEN && + !_local_table->is_max_size_reached()) { + log_debug(symboltable)("Choosing growing over rehashing."); + trigger_concurrent_work(); + _needs_rehashing = false; + return; + } - if (soc->writing()) { - // Sanity. Make sure we don't use the shared table at dump time - _shared_table.reset(); + // Already rehashed. + if (rehashed) { + log_warning(symboltable)("Rehashing already done, still long lists."); + trigger_concurrent_work(); + _needs_rehashing = false; + return; } -#endif + + murmur_seed = AltHashing::compute_seed(); + + if (do_rehash()) { + rehashed = true; + } else { + log_info(symboltable)("Resizes in progress rehashing skipped."); + } + + _needs_rehashing = false; +} + +void SymbolTable::rehash_table() { + SymbolTable::the_table()->try_rehash_table(); } //--------------------------------------------------------------------------- @@ -634,89 +856,80 @@ #ifndef PRODUCT -void SymbolTable::print_histogram() { - MutexLocker ml(SymbolTable_lock); - const int results_length = 100; - int counts[results_length]; - int sizes[results_length]; - int i,j; - - // initialize results to zero - for (j = 0; j < results_length; j++) { - counts[j] = 0; - sizes[j] = 0; - } - - int total_size = 0; - int total_count = 0; - int total_length = 0; - int max_length = 0; - int out_of_range_count = 0; - int out_of_range_size = 0; - for (i = 0; i < the_table()->table_size(); i++) { - HashtableEntry* p = the_table()->bucket(i); - for ( ; p != NULL; p = p->next()) { - int size = p->literal()->size(); - int len = p->literal()->utf8_length(); - if (len < results_length) { - counts[len]++; - sizes[len] += size; - } else { - out_of_range_count++; - out_of_range_size += size; - } - total_count++; - total_size += size; - total_length += len; - max_length = MAX2(max_length, len); +class HistogramIterator : StackObj { +public: + static const size_t results_length = 100; + size_t counts[results_length]; + size_t sizes[results_length]; + size_t total_size; + size_t total_count; + size_t total_length; + size_t max_length; + size_t out_of_range_count; + size_t out_of_range_size; + HistogramIterator() : total_size(0), total_count(0), total_length(0), + max_length(0), out_of_range_count(0), out_of_range_size(0) { + // initialize results to zero + for (size_t i = 0; i < results_length; i++) { + counts[i] = 0; + sizes[i] = 0; + } + } + bool operator()(Symbol** value) { + assert(value != NULL, "expected valid value"); + assert(*value != NULL, "value should point to a symbol"); + Symbol* sym = *value; + size_t size = sym->size(); + size_t len = sym->utf8_length(); + if (len < results_length) { + counts[len]++; + sizes[len] += size; + } else { + out_of_range_count++; + out_of_range_size += size; } - } + total_count++; + total_size += size; + total_length += len; + max_length = MAX2(max_length, len); + + return true; + }; +}; + +void SymbolTable::print_histogram() { + SymbolTable* st = SymbolTable::the_table(); + HistogramIterator hi; + st->_local_table->do_scan(Thread::current(), hi); tty->print_cr("Symbol Table Histogram:"); - tty->print_cr(" Total number of symbols %7d", total_count); - tty->print_cr(" Total size in memory %7dK", - (total_size*wordSize)/1024); - tty->print_cr(" Total counted %7d", _symbols_counted); - tty->print_cr(" Total removed %7d", _symbols_removed); - if (_symbols_counted > 0) { + tty->print_cr(" Total number of symbols " SIZE_FORMAT_W(7), hi.total_count); + tty->print_cr(" Total size in memory " SIZE_FORMAT_W(7) "K", + (hi.total_size * wordSize) / 1024); + tty->print_cr(" Total counted " SIZE_FORMAT_W(7), st->_symbols_counted); + tty->print_cr(" Total removed " SIZE_FORMAT_W(7), st->_symbols_removed); + if (SymbolTable::the_table()->_symbols_counted > 0) { tty->print_cr(" Percent removed %3.2f", - ((float)_symbols_removed/(float)_symbols_counted)* 100); + ((float)st->_symbols_removed / st->_symbols_counted) * 100); } - tty->print_cr(" Reference counts %7d", Symbol::_total_count); - tty->print_cr(" Symbol arena used " SIZE_FORMAT_W(7) "K", arena()->used()/1024); - tty->print_cr(" Symbol arena size " SIZE_FORMAT_W(7) "K", arena()->size_in_bytes()/1024); - tty->print_cr(" Total symbol length %7d", total_length); - tty->print_cr(" Maximum symbol length %7d", max_length); - tty->print_cr(" Average symbol length %7.2f", ((float) total_length / (float) total_count)); + tty->print_cr(" Reference counts " SIZE_FORMAT_W(7), Symbol::_total_count); + tty->print_cr(" Symbol arena used " SIZE_FORMAT_W(7) "K", arena()->used() / 1024); + tty->print_cr(" Symbol arena size " SIZE_FORMAT_W(7) "K", arena()->size_in_bytes() / 1024); + tty->print_cr(" Total symbol length " SIZE_FORMAT_W(7), hi.total_length); + tty->print_cr(" Maximum symbol length " SIZE_FORMAT_W(7), hi.max_length); + tty->print_cr(" Average symbol length %7.2f", ((float)hi.total_length / hi.total_count)); tty->print_cr(" Symbol length histogram:"); tty->print_cr(" %6s %10s %10s", "Length", "#Symbols", "Size"); - for (i = 0; i < results_length; i++) { - if (counts[i] > 0) { - tty->print_cr(" %6d %10d %10dK", i, counts[i], (sizes[i]*wordSize)/1024); - } - } - tty->print_cr(" >=%6d %10d %10dK\n", results_length, - out_of_range_count, (out_of_range_size*wordSize)/1024); -} - -void SymbolTable::print() { - for (int i = 0; i < the_table()->table_size(); ++i) { - HashtableEntry** p = the_table()->bucket_addr(i); - HashtableEntry* entry = the_table()->bucket(i); - if (entry != NULL) { - while (entry != NULL) { - tty->print(PTR_FORMAT " ", p2i(entry->literal())); - entry->literal()->print(); - tty->print(" %d", entry->literal()->refcount()); - p = entry->next_addr(); - entry = (HashtableEntry*)HashtableEntry::make_ptr(*p); - } - tty->cr(); + for (size_t i = 0; i < hi.results_length; i++) { + if (hi.counts[i] > 0) { + tty->print_cr(" " SIZE_FORMAT_W(6) " " SIZE_FORMAT_W(10) " " SIZE_FORMAT_W(10) "K", + i, hi.counts[i], (hi.sizes[i] * wordSize) / 1024); } } + tty->print_cr(" >=" SIZE_FORMAT_W(6) " " SIZE_FORMAT_W(10) " " SIZE_FORMAT_W(10) "K\n", + hi.results_length, hi.out_of_range_count, (hi.out_of_range_size*wordSize) / 1024); } #endif // PRODUCT - // Utility for dumping symbols SymboltableDCmd::SymboltableDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), --- old/src/hotspot/share/classfile/symbolTable.hpp 2018-08-03 16:41:12.000000000 -0500 +++ new/src/hotspot/share/classfile/symbolTable.hpp 2018-08-03 16:41:12.000000000 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2018, 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 @@ -26,23 +26,11 @@ #define SHARE_VM_CLASSFILE_SYMBOLTABLE_HPP #include "memory/allocation.hpp" +#include "memory/padded.hpp" #include "oops/symbol.hpp" +#include "utilities/concurrentHashTable.hpp" #include "utilities/hashtable.hpp" -// The symbol table holds all Symbol*s and corresponding interned strings. -// Symbol*s and literal strings should be canonicalized. -// -// The interned strings are created lazily. -// -// It is implemented as an open hash table with a fixed number of buckets. -// -// %note: -// - symbolTableEntrys are allocated in blocks to reduce the space overhead. - -class BoolObjectClosure; -class outputStream; -class SerializeClosure; - // TempNewSymbol acts as a handle class in a handle/body idiom and is // responsible for proper resource management of the body (which is a Symbol*). // The body is resource managed by a reference counting scheme. @@ -59,7 +47,7 @@ class TempNewSymbol : public StackObj { Symbol* _temp; - public: +public: TempNewSymbol() : _temp(NULL) {} // Conversion from a Symbol* to a TempNewSymbol. @@ -97,35 +85,69 @@ }; template class CompactHashtable; +class CompactSymbolTableWriter; +class SerializeClosure; + +class SymbolTableConfig; +typedef ConcurrentHashTable SymbolTableHash; -class SymbolTable : public RehashableHashtable { +class SymbolTableCreateEntry; + +class SymbolTable : public CHeapObj { friend class VMStructs; + friend class Symbol; friend class ClassFileParser; + friend class SymbolTableConfig; + friend class SymbolTableCreateEntry; private: + static void delete_symbol(Symbol* sym); + void grow(JavaThread* jt); + void clean_dead_entries(JavaThread* jt); + // The symbol table static SymbolTable* _the_table; + // Shared symbol table. + static CompactHashtable _shared_table; + static volatile bool _lookup_shared_first; + static volatile bool _alt_hash; + + // For statistics + volatile size_t _symbols_removed; + volatile size_t _symbols_counted; + SymbolTableHash* _local_table; + size_t _current_size; + volatile bool _has_work; // Set if one bucket is out of balance due to hash algorithm deficiency - static bool _needs_rehashing; - static bool _lookup_shared_first; + volatile bool _needs_rehashing; - // For statistics - static int _symbols_removed; - static int _symbols_counted; + volatile size_t _items_count; + volatile size_t _uncleaned_items_count; - // shared symbol table. - static CompactHashtable _shared_table; + double get_load_factor(); + double get_dead_factor(); - Symbol* allocate_symbol(const u1* name, int len, bool c_heap, TRAPS); // Assumes no characters larger than 0x7F + void check_concurrent_work(); + void trigger_concurrent_work(); + + static void item_added(); + static void item_removed(); + static void set_item_clean_count(size_t ncl); + static void mark_item_clean_count(); + + SymbolTable(); + + Symbol* allocate_symbol(const char* name, int len, bool c_heap, TRAPS); // Assumes no characters larger than 0x7F + Symbol* do_lookup(const char* name, int len, uintx hash); + Symbol* do_add_if_needed(const char* name, int len, uintx hash, bool heap, TRAPS); // Adding elements - Symbol* basic_add(int index, u1* name, int len, unsigned int hashValue, - bool c_heap, TRAPS); - bool basic_add(ClassLoaderData* loader_data, - const constantPoolHandle& cp, int names_count, - const char** names, int* lengths, int* cp_indices, - unsigned int* hashValues, TRAPS); + static void add(ClassLoaderData* loader_data, + const constantPoolHandle& cp, int names_count, + const char** names, int* lengths, int* cp_indices, + unsigned int* hashValues, TRAPS); static void new_symbols(ClassLoaderData* loader_data, const constantPoolHandle& cp, int names_count, @@ -136,15 +158,8 @@ } static Symbol* lookup_shared(const char* name, int len, unsigned int hash); - Symbol* lookup_dynamic(int index, const char* name, int len, unsigned int hash); - Symbol* lookup(int index, const char* name, int len, unsigned int hash); - - SymbolTable() - : RehashableHashtable(SymbolTableSize, sizeof (HashtableEntry)) {} - - SymbolTable(HashtableBucket* t, int number_of_entries) - : RehashableHashtable(SymbolTableSize, sizeof (HashtableEntry), t, - number_of_entries) {} + Symbol* lookup_dynamic(const char* name, int len, unsigned int hash); + Symbol* lookup_common(const char* name, int len, unsigned int hash); // Arena for permanent symbols (null class loader) that are never unloaded static Arena* _arena; @@ -152,121 +167,96 @@ static void initialize_symbols(int arena_alloc_size = 0); - static volatile int _parallel_claimed_idx; + void concurrent_work(JavaThread* jt); + void print_table_statistics(outputStream* st, const char* table_name); + + void try_rehash_table(); + bool do_rehash(); - typedef SymbolTable::BucketUnlinkContext BucketUnlinkContext; - // Release any dead symbols. Unlinked bucket entries are collected in the given - // context to be freed later. - // This allows multiple threads to work on the table at once. - static void buckets_unlink(int start_idx, int end_idx, BucketUnlinkContext* context); public: + // The symbol table + static SymbolTable* the_table() { return _the_table; } + size_t table_size(Thread* thread = NULL); + enum { symbol_alloc_batch_size = 8, // Pick initial size based on java -version size measurements - symbol_alloc_arena_size = 360*K + symbol_alloc_arena_size = 360*K // TODO (revisit) }; - // The symbol table - static SymbolTable* the_table() { return _the_table; } - - // Size of one bucket in the string table. Used when checking for rollover. - static uint bucket_size() { return sizeof(HashtableBucket); } - static void create_table() { assert(_the_table == NULL, "One symbol table allowed."); _the_table = new SymbolTable(); initialize_symbols(symbol_alloc_arena_size); } - static unsigned int hash_symbol(const char* s, int len); - static unsigned int hash_shared_symbol(const char* s, int len); + static void unlink() { + do_check_concurrent_work(); + } + static void do_check_concurrent_work(); + static void do_concurrent_work(JavaThread* jt); + static bool has_work() { return the_table()->_has_work; } + // Probing static Symbol* lookup(const char* name, int len, TRAPS); // lookup only, won't add. Also calculate hash. static Symbol* lookup_only(const char* name, int len, unsigned int& hash); // Only copy to C string to be added if lookup failed. static Symbol* lookup(const Symbol* sym, int begin, int end, TRAPS); - - static void release(Symbol* sym); - - // Look up the address of the literal in the SymbolTable for this Symbol* - static Symbol** lookup_symbol_addr(Symbol* sym); - // jchar (UTF16) version of lookups static Symbol* lookup_unicode(const jchar* name, int len, TRAPS); static Symbol* lookup_only_unicode(const jchar* name, int len, unsigned int& hash); - - static void add(ClassLoaderData* loader_data, - const constantPoolHandle& cp, int names_count, - const char** names, int* lengths, int* cp_indices, - unsigned int* hashValues, TRAPS); - - // Release any dead symbols - static void unlink() { - int processed = 0; - int removed = 0; - unlink(&processed, &removed); - } - static void unlink(int* processed, int* removed); - // Release any dead symbols, possibly parallel version - static void possibly_parallel_unlink(int* processed, int* removed); - - // iterate over symbols - static void symbols_do(SymbolClosure *cl); - static void metaspace_pointers_do(MetaspaceClosure* it); + // Needed for preloading classes in signatures when compiling. + // Returns the symbol is already present in symbol table, otherwise + // NULL. NO ALLOCATION IS GUARANTEED! + static Symbol* probe(const char* name, int len) { + unsigned int ignore_hash; + return lookup_only(name, len, ignore_hash); + } + static Symbol* probe_unicode(const jchar* name, int len) { + unsigned int ignore_hash; + return lookup_only_unicode(name, len, ignore_hash); + } // Symbol creation static Symbol* new_symbol(const char* utf8_buffer, int length, TRAPS) { assert(utf8_buffer != NULL, "just checking"); return lookup(utf8_buffer, length, THREAD); } - static Symbol* new_symbol(const char* name, TRAPS) { + static Symbol* new_symbol(const char* name, TRAPS) { return new_symbol(name, (int)strlen(name), THREAD); } - static Symbol* new_symbol(const Symbol* sym, int begin, int end, TRAPS) { + static Symbol* new_symbol(const Symbol* sym, int begin, int end, TRAPS) { assert(begin <= end && end <= sym->utf8_length(), "just checking"); return lookup(sym, begin, end, THREAD); } - // Create a symbol in the arena for symbols that are not deleted static Symbol* new_permanent_symbol(const char* name, TRAPS); - // Symbol lookup - static Symbol* lookup(int index, const char* name, int len, TRAPS); + // Rehash the string table if it gets out of balance + static void rehash_table(); + static bool needs_rehashing() + { return SymbolTable::the_table()->_needs_rehashing; } - // Needed for preloading classes in signatures when compiling. - // Returns the symbol is already present in symbol table, otherwise - // NULL. NO ALLOCATION IS GUARANTEED! - static Symbol* probe(const char* name, int len) { - unsigned int ignore_hash; - return lookup_only(name, len, ignore_hash); - } - static Symbol* probe_unicode(const jchar* name, int len) { - unsigned int ignore_hash; - return lookup_only_unicode(name, len, ignore_hash); - } + // Heap dumper and CDS + static void symbols_do(SymbolClosure *cl); - // Histogram - static void print_histogram() PRODUCT_RETURN; - static void print() PRODUCT_RETURN; + // Sharing +private: + static void copy_shared_symbol_table(CompactSymbolTableWriter* ch_table); +public: + static void write_to_archive() NOT_CDS_RETURN; + static void serialize(SerializeClosure* soc) NOT_CDS_RETURN; + static void metaspace_pointers_do(MetaspaceClosure* it); + // Jcmd + static void dump(outputStream* st, bool verbose=false); // Debugging static void verify(); - static void dump(outputStream* st, bool verbose=false); static void read(const char* filename, TRAPS); - // Sharing - static void write_to_archive(); - static void serialize(SerializeClosure* soc); - static u4 encode_shared(Symbol* sym); - static Symbol* decode_shared(u4 offset); - - // Rehash the symbol table if it gets out of balance - static void rehash_table(); - static bool needs_rehashing() { return _needs_rehashing; } - // Parallel chunked scanning - static void clear_parallel_claimed_index() { _parallel_claimed_idx = 0; } - static int parallel_claimed_index() { return _parallel_claimed_idx; } + // Histogram + static void print_histogram() PRODUCT_RETURN; }; #endif // SHARE_VM_CLASSFILE_SYMBOLTABLE_HPP --- old/src/hotspot/share/gc/g1/g1CollectedHeap.cpp 2018-08-03 16:41:12.000000000 -0500 +++ new/src/hotspot/share/gc/g1/g1CollectedHeap.cpp 2018-08-03 16:41:12.000000000 -0500 @@ -25,7 +25,6 @@ #include "precompiled.hpp" #include "classfile/metadataOnStackMark.hpp" #include "classfile/stringTable.hpp" -#include "classfile/symbolTable.hpp" #include "code/codeCache.hpp" #include "code/icBuffer.hpp" #include "gc/g1/g1Allocator.inline.hpp" @@ -3235,56 +3234,40 @@ undo_waste * HeapWordSize / K); } -class G1StringAndSymbolCleaningTask : public AbstractGangTask { +class G1StringCleaningTask : public AbstractGangTask { private: BoolObjectClosure* _is_alive; G1StringDedupUnlinkOrOopsDoClosure _dedup_closure; OopStorage::ParState _par_state_string; int _initial_string_table_size; - int _initial_symbol_table_size; bool _process_strings; int _strings_processed; int _strings_removed; - bool _process_symbols; - int _symbols_processed; - int _symbols_removed; - bool _process_string_dedup; public: - G1StringAndSymbolCleaningTask(BoolObjectClosure* is_alive, bool process_strings, bool process_symbols, bool process_string_dedup) : - AbstractGangTask("String/Symbol Unlinking"), + G1StringCleaningTask(BoolObjectClosure* is_alive, bool process_strings, bool process_string_dedup) : + AbstractGangTask("String Unlinking"), _is_alive(is_alive), _dedup_closure(is_alive, NULL, false), _par_state_string(StringTable::weak_storage()), _process_strings(process_strings), _strings_processed(0), _strings_removed(0), - _process_symbols(process_symbols), _symbols_processed(0), _symbols_removed(0), _process_string_dedup(process_string_dedup) { _initial_string_table_size = (int) StringTable::the_table()->table_size(); - _initial_symbol_table_size = SymbolTable::the_table()->table_size(); - if (process_symbols) { - SymbolTable::clear_parallel_claimed_index(); - } if (process_strings) { StringTable::reset_dead_counter(); } } - ~G1StringAndSymbolCleaningTask() { - guarantee(!_process_symbols || SymbolTable::parallel_claimed_index() >= _initial_symbol_table_size, - "claim value %d after unlink less than initial symbol table size %d", - SymbolTable::parallel_claimed_index(), _initial_symbol_table_size); - + ~G1StringCleaningTask() { log_info(gc, stringtable)( - "Cleaned string and symbol table, " - "strings: " SIZE_FORMAT " processed, " SIZE_FORMAT " removed, " - "symbols: " SIZE_FORMAT " processed, " SIZE_FORMAT " removed", - strings_processed(), strings_removed(), - symbols_processed(), symbols_removed()); + "Cleaned string table, " + "strings: " SIZE_FORMAT " processed, " SIZE_FORMAT " removed", + strings_processed(), strings_removed()); if (_process_strings) { StringTable::finish_dead_counter(); } @@ -3293,18 +3276,11 @@ void work(uint worker_id) { int strings_processed = 0; int strings_removed = 0; - int symbols_processed = 0; - int symbols_removed = 0; if (_process_strings) { StringTable::possibly_parallel_unlink(&_par_state_string, _is_alive, &strings_processed, &strings_removed); Atomic::add(strings_processed, &_strings_processed); Atomic::add(strings_removed, &_strings_removed); } - if (_process_symbols) { - SymbolTable::possibly_parallel_unlink(&symbols_processed, &symbols_removed); - Atomic::add(symbols_processed, &_symbols_processed); - Atomic::add(symbols_removed, &_symbols_removed); - } if (_process_string_dedup) { G1StringDedup::parallel_unlink(&_dedup_closure, worker_id); } @@ -3312,9 +3288,6 @@ size_t strings_processed() const { return (size_t)_strings_processed; } size_t strings_removed() const { return (size_t)_strings_removed; } - - size_t symbols_processed() const { return (size_t)_symbols_processed; } - size_t symbols_removed() const { return (size_t)_symbols_removed; } }; class G1CodeCacheUnloadingTask { @@ -3564,7 +3537,7 @@ class G1ParallelCleaningTask : public AbstractGangTask { private: bool _unloading_occurred; - G1StringAndSymbolCleaningTask _string_symbol_task; + G1StringCleaningTask _string_task; G1CodeCacheUnloadingTask _code_cache_task; G1KlassCleaningTask _klass_cleaning_task; G1ResolvedMethodCleaningTask _resolved_method_cleaning_task; @@ -3573,7 +3546,7 @@ // The constructor is run in the VMThread. G1ParallelCleaningTask(BoolObjectClosure* is_alive, uint num_workers, bool unloading_occurred) : AbstractGangTask("Parallel Cleaning"), - _string_symbol_task(is_alive, true, true, G1StringDedup::is_enabled()), + _string_task(is_alive, true, G1StringDedup::is_enabled()), _code_cache_task(num_workers, is_alive, unloading_occurred), _klass_cleaning_task(), _unloading_occurred(unloading_occurred), @@ -3588,8 +3561,8 @@ // Let the threads mark that the first pass is done. _code_cache_task.barrier_mark(worker_id); - // Clean the Strings and Symbols. - _string_symbol_task.work(worker_id); + // Clean the Strings. + _string_task.work(worker_id); // Clean unreferenced things in the ResolvedMethodTable _resolved_method_cleaning_task.work(); @@ -3621,16 +3594,14 @@ void G1CollectedHeap::partial_cleaning(BoolObjectClosure* is_alive, bool process_strings, - bool process_symbols, bool process_string_dedup) { - if (!process_strings && !process_symbols && !process_string_dedup) { + if (!process_strings && !process_string_dedup) { // Nothing to clean. return; } - G1StringAndSymbolCleaningTask g1_unlink_task(is_alive, process_strings, process_symbols, process_string_dedup); + G1StringCleaningTask g1_unlink_task(is_alive, process_strings, process_string_dedup); workers()->run_task(&g1_unlink_task); - } class G1RedirtyLoggedCardsTask : public AbstractGangTask { @@ -4024,7 +3995,7 @@ process_discovered_references(per_thread_states); // FIXME - // CM's reference processing also cleans up the string and symbol tables. + // CM's reference processing also cleans up the string table. // Should we do that here also? We could, but it is a serial operation // and could significantly increase the pause time. --- old/src/hotspot/share/gc/g1/g1CollectedHeap.hpp 2018-08-03 16:41:13.000000000 -0500 +++ new/src/hotspot/share/gc/g1/g1CollectedHeap.hpp 2018-08-03 16:41:13.000000000 -0500 @@ -1327,9 +1327,8 @@ // Partial cleaning used when class unloading is disabled. // Let the caller choose what structures to clean out: // - StringTable - // - SymbolTable // - StringDeduplication structures - void partial_cleaning(BoolObjectClosure* is_alive, bool unlink_strings, bool unlink_symbols, bool unlink_string_dedup); + void partial_cleaning(BoolObjectClosure* is_alive, bool unlink_strings, bool unlink_string_dedup); // Complete cleaning used when class unloading is enabled. // Cleans out all structures handled by partial_cleaning and also the CodeCache. --- old/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp 2018-08-03 16:41:13.000000000 -0500 +++ new/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp 2018-08-03 16:41:13.000000000 -0500 @@ -24,7 +24,6 @@ #include "precompiled.hpp" #include "classfile/metadataOnStackMark.hpp" -#include "classfile/symbolTable.hpp" #include "code/codeCache.hpp" #include "gc/g1/g1BarrierSet.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" @@ -1579,8 +1578,8 @@ // Is alive closure. G1CMIsAliveClosure g1_is_alive(_g1h); - // Inner scope to exclude the cleaning of the string and symbol - // tables from the displayed time. + // Inner scope to exclude the cleaning of the string table + // from the displayed time. { GCTraceTime(Debug, gc, phases) debug("Reference Processing", _gc_timer_cm); @@ -1674,16 +1673,16 @@ WeakProcessor::weak_oops_do(&g1_is_alive, &do_nothing_cl); } - // Unload Klasses, String, Symbols, Code Cache, etc. + // Unload Klasses, String, Code Cache, etc. if (ClassUnloadingWithConcurrentMark) { GCTraceTime(Debug, gc, phases) debug("Class Unloading", _gc_timer_cm); bool purged_classes = SystemDictionary::do_unloading(_gc_timer_cm, false /* Defer cleaning */); _g1h->complete_cleaning(&g1_is_alive, purged_classes); } else { GCTraceTime(Debug, gc, phases) debug("Cleanup", _gc_timer_cm); - // No need to clean string table and symbol table as they are treated as strong roots when + // No need to clean string table as it is treated as strong roots when // class unloading is disabled. - _g1h->partial_cleaning(&g1_is_alive, false, false, G1StringDedup::is_enabled()); + _g1h->partial_cleaning(&g1_is_alive, false, G1StringDedup::is_enabled()); } } --- old/src/hotspot/share/gc/g1/g1FullCollector.cpp 2018-08-03 16:41:14.000000000 -0500 +++ new/src/hotspot/share/gc/g1/g1FullCollector.cpp 2018-08-03 16:41:14.000000000 -0500 @@ -226,8 +226,8 @@ _heap->complete_cleaning(&_is_alive, purged_class); } else { GCTraceTime(Debug, gc, phases) debug("Phase 1: String and Symbol Tables Cleanup", scope()->timer()); - // If no class unloading just clean out strings and symbols. - _heap->partial_cleaning(&_is_alive, true, true, G1StringDedup::is_enabled()); + // If no class unloading just clean out strings. + _heap->partial_cleaning(&_is_alive, true, G1StringDedup::is_enabled()); } scope()->tracer()->report_object_count_after_gc(&_is_alive); --- old/src/hotspot/share/gc/z/zRootsIterator.cpp 2018-08-03 16:41:15.000000000 -0500 +++ new/src/hotspot/share/gc/z/zRootsIterator.cpp 2018-08-03 16:41:14.000000000 -0500 @@ -24,7 +24,6 @@ #include "precompiled.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/stringTable.hpp" -#include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "code/codeCache.hpp" #include "compiler/oopMap.hpp" @@ -74,7 +73,6 @@ static const ZStatSubPhase ZSubPhasePauseWeakRootsJNIWeakHandles("Pause Weak Roots JNIWeakHandles"); static const ZStatSubPhase ZSubPhasePauseWeakRootsJVMTIWeakExport("Pause Weak Roots JVMTIWeakExport"); static const ZStatSubPhase ZSubPhasePauseWeakRootsJFRWeak("Pause Weak Roots JFRWeak"); -static const ZStatSubPhase ZSubPhasePauseWeakRootsSymbolTable("Pause Weak Roots SymbolTable"); static const ZStatSubPhase ZSubPhasePauseWeakRootsStringTable("Pause Weak Roots StringTable"); static const ZStatSubPhase ZSubPhaseConcurrentWeakRoots("Concurrent Weak Roots"); @@ -302,11 +300,9 @@ _jfr_weak(this), _vm_weak_handles(this), _jni_weak_handles(this), - _symbol_table(this), _string_table(this) { assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint"); ZStatTimer timer(ZSubPhasePauseWeakRootsSetup); - SymbolTable::clear_parallel_claimed_index(); StringTable::reset_dead_counter(); } @@ -337,12 +333,6 @@ #endif } -void ZWeakRootsIterator::do_symbol_table(BoolObjectClosure* is_alive, OopClosure* cl) { - ZStatTimer timer(ZSubPhasePauseWeakRootsSymbolTable); - int dummy; - SymbolTable::possibly_parallel_unlink(&dummy, &dummy); -} - class ZStringTableDeadCounterBoolObjectClosure : public BoolObjectClosure { private: BoolObjectClosure* const _cl; @@ -375,9 +365,6 @@ void ZWeakRootsIterator::weak_oops_do(BoolObjectClosure* is_alive, OopClosure* cl) { ZStatTimer timer(ZSubPhasePauseWeakRoots); - if (ZSymbolTableUnloading) { - _symbol_table.weak_oops_do(is_alive, cl); - } if (ZWeakRoots) { _jvmti_weak_export.weak_oops_do(is_alive, cl); _jfr_weak.weak_oops_do(is_alive, cl); --- old/src/hotspot/share/gc/z/zRootsIterator.hpp 2018-08-03 16:41:15.000000000 -0500 +++ new/src/hotspot/share/gc/z/zRootsIterator.hpp 2018-08-03 16:41:15.000000000 -0500 @@ -130,14 +130,12 @@ void do_jni_weak_handles(BoolObjectClosure* is_alive, OopClosure* cl); void do_jvmti_weak_export(BoolObjectClosure* is_alive, OopClosure* cl); void do_jfr_weak(BoolObjectClosure* is_alive, OopClosure* cl); - void do_symbol_table(BoolObjectClosure* is_alive, OopClosure* cl); void do_string_table(BoolObjectClosure* is_alive, OopClosure* cl); ZSerialWeakOopsDo _jvmti_weak_export; ZSerialWeakOopsDo _jfr_weak; ZParallelWeakOopsDo _vm_weak_handles; ZParallelWeakOopsDo _jni_weak_handles; - ZParallelWeakOopsDo _symbol_table; ZParallelWeakOopsDo _string_table; public: --- old/src/hotspot/share/gc/z/z_globals.hpp 2018-08-03 16:41:15.000000000 -0500 +++ new/src/hotspot/share/gc/z/z_globals.hpp 2018-08-03 16:41:15.000000000 -0500 @@ -79,9 +79,6 @@ diagnostic(bool, ZVerifyForwarding, false, \ "Verify forwarding tables") \ \ - diagnostic(bool, ZSymbolTableUnloading, false, \ - "Unload unused VM symbols") \ - \ diagnostic(bool, ZWeakRoots, true, \ "Treat JNI WeakGlobalRefs and StringTable as weak roots") \ \ --- old/src/hotspot/share/logging/logPrefix.hpp 2018-08-03 16:41:16.000000000 -0500 +++ new/src/hotspot/share/logging/logPrefix.hpp 2018-08-03 16:41:16.000000000 -0500 @@ -80,6 +80,7 @@ LOG_PREFIX(GCId::print_prefix, LOG_TAGS(gc, reloc)) \ LOG_PREFIX(GCId::print_prefix, LOG_TAGS(gc, start)) \ LOG_PREFIX(GCId::print_prefix, LOG_TAGS(gc, stringtable)) \ + LOG_PREFIX(GCId::print_prefix, LOG_TAGS(gc, symboltable)) \ LOG_PREFIX(GCId::print_prefix, LOG_TAGS(gc, sweep)) \ LOG_PREFIX(GCId::print_prefix, LOG_TAGS(gc, task)) \ LOG_PREFIX(GCId::print_prefix, LOG_TAGS(gc, task, start)) \ --- old/src/hotspot/share/logging/logTag.hpp 2018-08-03 16:41:16.000000000 -0500 +++ new/src/hotspot/share/logging/logTag.hpp 2018-08-03 16:41:16.000000000 -0500 @@ -147,6 +147,7 @@ LOG_TAG(stats) \ LOG_TAG(stringdedup) \ LOG_TAG(stringtable) \ + LOG_TAG(symboltable) \ LOG_TAG(stackmap) \ LOG_TAG(subclass) \ LOG_TAG(survivor) \ --- old/src/hotspot/share/memory/arena.hpp 2018-08-03 16:41:17.000000000 -0500 +++ new/src/hotspot/share/memory/arena.hpp 2018-08-03 16:41:17.000000000 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2018, 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 @@ -199,12 +199,18 @@ } // Fast delete in area. Common case is: NOP (except for storage reclaimed) - void Afree(void *ptr, size_t size) { + bool Afree(void *ptr, size_t size) { #ifdef ASSERT if (ZapResourceArea) memset(ptr, badResourceValue, size); // zap freed memory - if (UseMallocOnly) return; + if (UseMallocOnly) return true; #endif - if (((char*)ptr) + size == _hwm) _hwm = (char*)ptr; + if (((char*)ptr) + size == _hwm) { + _hwm = (char*)ptr; + return true; + } else { + // Unable to fast free, so we just drop it. + return false; + } } void *Arealloc( void *old_ptr, size_t old_size, size_t new_size, --- old/src/hotspot/share/oops/symbol.cpp 2018-08-03 16:41:17.000000000 -0500 +++ new/src/hotspot/share/oops/symbol.cpp 2018-08-03 16:41:17.000000000 -0500 @@ -318,4 +318,4 @@ } // SymbolTable prints this in its statistics -NOT_PRODUCT(int Symbol::_total_count = 0;) +NOT_PRODUCT(size_t Symbol::_total_count = 0;) --- old/src/hotspot/share/oops/symbol.hpp 2018-08-03 16:41:18.000000000 -0500 +++ new/src/hotspot/share/oops/symbol.hpp 2018-08-03 16:41:17.000000000 -0500 @@ -256,7 +256,7 @@ // only for getting its vtable pointer. Symbol() { } - static int _total_count; + static size_t _total_count; #endif }; --- old/src/hotspot/share/runtime/serviceThread.cpp 2018-08-03 16:41:18.000000000 -0500 +++ new/src/hotspot/share/runtime/serviceThread.cpp 2018-08-03 16:41:18.000000000 -0500 @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "classfile/stringTable.hpp" +#include "classfile/symbolTable.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaCalls.hpp" #include "runtime/serviceThread.hpp" @@ -84,6 +85,7 @@ bool has_dcmd_notification_event = false; bool acs_notify = false; bool stringtable_work = false; + bool symboltable_work = false; JvmtiDeferredEvent jvmti_event; { // Need state transition ThreadBlockInVM so that this thread @@ -101,7 +103,8 @@ !(has_jvmti_events = JvmtiDeferredEventQueue::has_events()) && !(has_gc_notification_event = GCNotifier::has_event()) && !(has_dcmd_notification_event = DCmdFactory::has_pending_jmx_notification()) && - !(stringtable_work = StringTable::has_work())) { + !(stringtable_work = StringTable::has_work()) && + !(symboltable_work = SymbolTable::has_work())) { // wait until one of the sensors has pending requests, or there is a // pending JVMTI event or JMX GC notification to post Service_lock->wait(Mutex::_no_safepoint_check_flag); @@ -116,6 +119,10 @@ StringTable::do_concurrent_work(jt); } + if (symboltable_work) { + SymbolTable::do_concurrent_work(jt); + } + if (has_jvmti_events) { jvmti_event.post(); } --- old/src/hotspot/share/runtime/vmStructs.cpp 2018-08-03 16:41:19.000000000 -0500 +++ new/src/hotspot/share/runtime/vmStructs.cpp 2018-08-03 16:41:18.000000000 -0500 @@ -162,12 +162,8 @@ typedef HashtableEntry IntptrHashtableEntry; typedef Hashtable IntptrHashtable; -typedef Hashtable SymbolHashtable; -typedef HashtableEntry SymbolHashtableEntry; typedef Hashtable KlassHashtable; typedef HashtableEntry KlassHashtableEntry; -typedef CompactHashtable SymbolCompactHashTable; -typedef RehashableHashtable RehashableSymbolHashtable; typedef PaddedEnd PaddedObjectMonitor; @@ -467,24 +463,6 @@ static_field(PerfMemory, _prologue, PerfDataPrologue*) \ static_field(PerfMemory, _initialized, int) \ \ - /***************/ \ - /* SymbolTable */ \ - /***************/ \ - \ - static_field(SymbolTable, _the_table, SymbolTable*) \ - static_field(SymbolTable, _shared_table, SymbolCompactHashTable) \ - static_field(RehashableSymbolHashtable, _seed, juint) \ - \ - /********************/ \ - /* CompactHashTable */ \ - /********************/ \ - \ - nonstatic_field(SymbolCompactHashTable, _base_address, address) \ - nonstatic_field(SymbolCompactHashTable, _entry_count, u4) \ - nonstatic_field(SymbolCompactHashTable, _bucket_count, u4) \ - nonstatic_field(SymbolCompactHashTable, _buckets, u4*) \ - nonstatic_field(SymbolCompactHashTable, _entries, u4*) \ - \ /********************/ \ /* SystemDictionary */ \ /********************/ \ @@ -1351,15 +1329,13 @@ declare_toplevel_type(PerfMemory) \ declare_type(PerfData, CHeapObj) \ \ - /*********************************/ \ - /* SymbolTable, SystemDictionary */ \ - /*********************************/ \ + /********************/ \ + /* SystemDictionary */ \ + /********************/ \ \ declare_toplevel_type(BasicHashtable) \ declare_type(IntptrHashtable, BasicHashtable) \ declare_toplevel_type(BasicHashtable) \ - declare_type(RehashableSymbolHashtable, BasicHashtable) \ - declare_type(SymbolTable, SymbolHashtable) \ declare_type(Dictionary, KlassHashtable) \ declare_toplevel_type(BasicHashtableEntry) \ declare_type(IntptrHashtableEntry, BasicHashtableEntry) \ @@ -1373,8 +1349,6 @@ declare_toplevel_type(Arena) \ declare_type(ResourceArea, Arena) \ \ - declare_toplevel_type(SymbolCompactHashTable) \ - \ /***********************************************************/ \ /* Thread hierarchy (needed for run-time type information) */ \ /***********************************************************/ \ --- old/src/hotspot/share/utilities/concurrentHashTable.hpp 2018-08-03 16:41:19.000000000 -0500 +++ new/src/hotspot/share/utilities/concurrentHashTable.hpp 2018-08-03 16:41:19.000000000 -0500 @@ -309,7 +309,7 @@ // Insert which handles a number of cases. template bool internal_insert(Thread* thread, LOOKUP_FUNC& lookup_f, VALUE_FUNC& value_f, - CALLBACK_FUNC& callback, bool* grow_hint = NULL); + CALLBACK_FUNC& callback, bool* grow_hint = NULL, bool* clean_hint = NULL); // Returns true if an item matching LOOKUP_FUNC is removed. // Calls DELETE_FUNC before destroying the node. @@ -396,8 +396,8 @@ // value already exists. template bool get_insert_lazy(Thread* thread, LOOKUP_FUNC& lookup_f, VALUE_FUNC& val_f, - CALLBACK_FUNC& callback_f, bool* grow_hint = NULL) { - return !internal_insert(thread, lookup_f, val_f, callback_f, grow_hint); + CALLBACK_FUNC& callback_f, bool* grow_hint = NULL, bool* clean_hint = NULL) { + return !internal_insert(thread, lookup_f, val_f, callback_f, grow_hint, clean_hint); } // Same without CALLBACK_FUNC. @@ -436,9 +436,9 @@ // LOOKUP_FUNC. template bool insert(Thread* thread, LOOKUP_FUNC& lookup_f, const VALUE& value, - bool* grow_hint = NULL) { + bool* grow_hint = NULL, bool* clean_hint = NULL) { LazyValueRetrieve vp(value); - return internal_insert(thread, lookup_f, vp, noOp, grow_hint); + return internal_insert(thread, lookup_f, vp, noOp, grow_hint, clean_hint); } // This does a fast unsafe insert and can thus only be used when there is no --- old/src/hotspot/share/utilities/concurrentHashTable.inline.hpp 2018-08-03 16:41:20.000000000 -0500 +++ new/src/hotspot/share/utilities/concurrentHashTable.inline.hpp 2018-08-03 16:41:20.000000000 -0500 @@ -540,6 +540,8 @@ inline void ConcurrentHashTable:: delete_in_bucket(Thread* thread, Bucket* bucket, LOOKUP_FUNC& lookup_f) { + assert(bucket->is_locked(), "Must be locked."); + size_t dels = 0; Node* ndel[BULK_DELETE_LIMIT]; Node* const volatile * rem_n_prev = bucket->first_ptr(); @@ -874,7 +876,7 @@ template inline bool ConcurrentHashTable:: internal_insert(Thread* thread, LOOKUP_FUNC& lookup_f, VALUE_FUNC& value_f, - CALLBACK_FUNC& callback, bool* grow_hint) + CALLBACK_FUNC& callback, bool* grow_hint, bool* clean_hint) { bool ret = false; bool clean = false; @@ -925,15 +927,20 @@ } else if (i == 0 && clean) { // We only do cleaning on fast inserts. Bucket* bucket = get_bucket_locked(thread, lookup_f.get_hash()); - assert(bucket->is_locked(), "Must be locked."); delete_in_bucket(thread, bucket, lookup_f); bucket->unlock(); + + clean = false; } if (grow_hint != NULL) { *grow_hint = loops > _grow_hint; } + if (clean_hint != NULL) { + *clean_hint = clean; + } + return ret; } --- old/src/hotspot/share/utilities/globalCounter.cpp 2018-08-03 16:41:20.000000000 -0500 +++ new/src/hotspot/share/utilities/globalCounter.cpp 2018-08-03 16:41:20.000000000 -0500 @@ -61,6 +61,11 @@ // Atomic::add must provide fence since we have storeload dependency. volatile uintx gbl_cnt = Atomic::add((uintx)COUNTER_INCREMENT, &_global_counter._counter, memory_order_conservative); + // Handle bootstrap + if (Threads::number_of_threads() == 0) { + return; + } + // Do all RCU threads. CounterThreadCheck ctc(gbl_cnt); for (JavaThreadIteratorWithHandle jtiwh; JavaThread *thread = jtiwh.next(); ) { --- old/src/hotspot/share/utilities/globalDefinitions.hpp 2018-08-03 16:41:21.000000000 -0500 +++ new/src/hotspot/share/utilities/globalDefinitions.hpp 2018-08-03 16:41:20.000000000 -0500 @@ -422,13 +422,14 @@ const int max_method_code_size = 64*K - 1; // JVM spec, 2nd ed. section 4.8.1 (p.134) //---------------------------------------------------------------------------------------------------- -// Default and minimum StringTableSize values +// Default and minimum StringTable and SymbolTable size values +// Must be a power of 2 const int defaultStringTableSize = NOT_LP64(1024) LP64_ONLY(65536); const int minimumStringTableSize = 128; -const int defaultSymbolTableSize = 20011; -const int minimumSymbolTableSize = 1009; +const int defaultSymbolTableSize = 32768; // 2^15 +const int minimumSymbolTableSize = 1024; //---------------------------------------------------------------------------------------------------- --- /dev/null 2018-08-03 16:41:21.000000000 -0500 +++ new/test/hotspot/jtreg/gc/g1/TestStringTableStats.java 2018-08-03 16:41:21.000000000 -0500 @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013, 2018, 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 TestStringTableStats.java + * @bug 8027476 8027455 + * @summary Ensure that the G1TraceStringTableScrubbing prints the expected message. + * @key gc + * @requires vm.gc.G1 + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestStringTableStats { + public static void main(String[] args) throws Exception { + + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-XX:+UseG1GC", + "-XX:+UnlockExperimentalVMOptions", + "-Xlog:gc+stringtable=trace", + SystemGCTest.class.getName()); + + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + + System.out.println("Output:\n" + output.getOutput()); + + output.shouldMatch("GC\\(\\d+\\) Cleaned string table"); + output.shouldHaveExitValue(0); + } + + static class SystemGCTest { + public static void main(String [] args) { + System.out.println("Calling System.gc()"); + System.gc(); + } + } +} --- /dev/null 2018-08-03 16:41:22.000000000 -0500 +++ new/test/hotspot/jtreg/runtime/symboltable/ShortLivedSymbolCleanup.java 2018-08-03 16:41:21.000000000 -0500 @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018, 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 8195100 + * @summary a short lived Symbol should be cleaned up + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @requires (vm.debug == true) + */ + +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import java.util.Scanner; + +public class ShortLivedSymbolCleanup { + + static int getSymbolTableSize(ProcessBuilder pb) throws Exception { + int size = 0; + + OutputAnalyzer analyzer = new OutputAnalyzer(pb.start()); + String output = analyzer.getStdout(); + analyzer.shouldHaveExitValue(0); + + // Split string into lines using platform independent end of line marker. + String[] lines = output.split("\\R"); + for (String line : lines) { + if (line.contains("Start size")) { + // ex. "[0.023s][trace][symboltable] Start size: 32768 (15)" + Scanner scanner = new Scanner(line); + scanner.next(); // skip "[0.023s][trace][symboltable]" + scanner.next(); // skip "Start" + scanner.next(); // skip "size:" + size = Integer.parseInt(scanner.next()); // process "32768" + scanner.close(); + } + } + + return size; + } + + static void analyzeOutputOn(int size, ProcessBuilder pb) throws Exception { + OutputAnalyzer analyzer = new OutputAnalyzer(pb.start()); + String output = analyzer.getStdout(); + analyzer.shouldHaveExitValue(0); + + // Split string into lines using platform independent end of line marker. + String[] lines = output.split("\\R"); + for (String line : lines) { + if (line.startsWith(" Total removed")) { + // ex. "Total removed 13309" + Scanner scanner = new Scanner(line); + scanner.next(); // skip "Total" + scanner.next(); // skip "removed" + int removed = Integer.parseInt(scanner.next()); // process "13309" + scanner.close(); + + if (removed < (size/2)) { + System.out.println(output); + // We should have removed at least half of the temporary Symbols + throw new RuntimeException("Did not clean dead temporary Symbols [removed:"+removed+", size:"+size+"]"); + } + } + } + } + + public static void main(String[] args) throws Exception { + + if (Platform.isDebugBuild()) { + { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xlog:symboltable=trace", + "-version"); + int size = getSymbolTableSize(pb); + + pb = ProcessTools.createJavaProcessBuilder("-XX:+PrintSymbolTableSizeHistogram", + LotsOfTempSymbols.class.getName(), + Integer.toString(size)); + analyzeOutputOn(size, pb); + } + } + } + + static class LotsOfTempSymbols { + public static void main(String [] args) { + int size = 2*Integer.parseInt(args[0]); + // Create enough temporary Symbols, that we are + // guranteed to insert into every bucket twice, + // and therefore have the table check for dead entries + for (int i=0; i