8195100: Use a low latency hashtable for SymbolTable
Summary: Used concurrentHashTable, similar to stringTable
Reviewed-by: coleenp, kbarrett, iklam, pliden
--- a/src/hotspot/share/classfile/classLoaderData.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/classfile/classLoaderData.cpp Tue Aug 14 18:42:14 2018 -0500
@@ -1418,6 +1418,28 @@
}
if (seen_dead_loader) {
+ data = _head;
+ while (data != NULL) {
+ // Remove entries in the dictionary of live class loader that have
+ // initiated loading classes in a dead class loader.
+ if (data->dictionary() != NULL) {
+ data->dictionary()->do_unloading();
+ }
+ // Walk a ModuleEntry's reads, and a PackageEntry's exports
+ // lists to determine if there are modules on those lists that are now
+ // dead and should be removed. A module's life cycle is equivalent
+ // to its defining class loader's life cycle. Since a module is
+ // considered dead if its class loader is dead, these walks must
+ // occur after each class loader's aliveness is determined.
+ if (data->packages() != NULL) {
+ data->packages()->purge_all_package_exports();
+ }
+ if (data->modules_defined()) {
+ data->modules()->purge_all_module_reads();
+ }
+ data = data->next();
+ }
+ SymbolTable::do_check_concurrent_work();
JFR_ONLY(post_class_unload_events();)
}
--- a/src/hotspot/share/classfile/compactHashtable.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/classfile/compactHashtable.hpp Tue Aug 14 18:42:14 2018 -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 T, class N> class CompactHashtable : public SimpleCompactHashtable {
--- a/src/hotspot/share/classfile/stringTable.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/classfile/stringTable.cpp Tue Aug 14 18:42:14 2018 -0500
@@ -64,9 +64,9 @@
// --------------------------------------------------------------------------
StringTable* StringTable::_the_table = NULL;
-bool StringTable::_shared_string_mapped = false;
CompactHashtable<oop, char> 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 ceil_log2(size_t 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 = ceil_log2(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,32 +195,31 @@
}
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) {
- return ((size_t)(1)) << _local_table->get_size_log2(thread != NULL ? thread
- : Thread::current());
+size_t StringTable::table_size() {
+ return ((size_t)1) << _local_table->get_size_log2(Thread::current());
}
void StringTable::trigger_concurrent_work() {
@@ -406,7 +405,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 +432,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;
@@ -465,7 +464,7 @@
}
}
gt.done(jt);
- _current_size = table_size(jt);
+ _current_size = table_size();
log_debug(stringtable)("Grown to size:" SIZE_FORMAT, _current_size);
}
@@ -843,7 +842,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);
--- a/src/hotspot/share/classfile/stringTable.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/classfile/stringTable.hpp Tue Aug 14 18:42:14 2018 -0500
@@ -58,21 +58,22 @@
static StringTable* _the_table;
// Shared string table
static CompactHashtable<oop, char> _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();
@@ -100,7 +101,7 @@
public:
// The string table
static StringTable* the_table() { return _the_table; }
- size_t table_size(Thread* thread = NULL);
+ size_t table_size();
static OopStorage* weak_storage() { return the_table()->_weak_handles; }
@@ -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.
--- a/src/hotspot/share/classfile/symbolTable.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/classfile/symbolTable.cpp Tue Aug 14 18:42:14 2018 -0500
@@ -27,46 +27,178 @@
#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"
+
+// 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
// --------------------------------------------------------------------------
-// the number of buckets a thread claims
-const int ClaimChunkSize = 32;
-
SymbolTable* SymbolTable::_the_table = NULL;
+CompactHashtable<Symbol*, char> 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;
+
+static juint murmur_seed = 0;
+
+static inline void log_trace_symboltable_helper(Symbol* sym, const char* msg) {
+#ifndef PRODUCT
+ ResourceMark rm;
+ 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);
+}
+
+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 ceil_log2(size_t value) {
+ size_t ret;
+ for (ret = 1; ((size_t)1 << ret) < value; ++ret);
+ return ret;
+}
-CompactHashtable<Symbol*, char> SymbolTable::_shared_table;
+SymbolTable::SymbolTable() :
+ _symbols_removed(0), _symbols_counted(0), _local_table(NULL),
+ _current_size(0), _has_work(0), _needs_rehashing(false),
+ _items_count(0), _uncleaned_items_count(0) {
+
+ size_t start_size_log_2 = ceil_log2(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));
+}
-Symbol* SymbolTable::allocate_symbol(const u1* name, int len, bool c_heap, TRAPS) {
+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() {
+ return ((size_t)1) << _local_table->get_size_log2(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 +212,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<Symbol*, mtSymbol>* p = the_table()->bucket(i);
- p != NULL;
- p = p->next()) {
- cl->do_symbol(p->literal_addr());
- }
- }
-}
-
-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<Symbol*, mtSymbol>* 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<Symbol*, mtSymbol>** p = the_table()->bucket_addr(i);
- HashtableEntry<Symbol*, mtSymbol>* 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<Symbol*, mtSymbol>*)HashtableEntry<Symbol*, mtSymbol>::make_ptr(*p);
- }
+ SymbolsDo sd(cl);
+ if (!SymbolTable::the_table()->_local_table->try_scan(Thread::current(), sd)) {
+ log_info(stringtable)("symbols_do unavailable at this moment");
}
}
-// 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;
-}
-
-void SymbolTable::possibly_parallel_unlink(int* processed, int* removed) {
- const int limit = the_table()->table_size();
+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;
+ };
+};
- 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);
+void SymbolTable::metaspace_pointers_do(MetaspaceClosure* it) {
+ assert(DumpSharedSpaces, "called only during dump time");
+ MetaspacePointersDo mpd(it);
+ SymbolTable::the_table()->_local_table->do_scan(Thread::current(), mpd);
}
-// 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<Symbol*, mtSymbol>* 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_dynamic(name, len, hash);
+ if (sym == NULL) {
+ sym = lookup_shared(name, len, hash);
+ if (sym != NULL) {
+ _lookup_shared_first = true;
+ }
}
- sym = lookup_shared(name, len, hash);
- if (sym != NULL) {
- _lookup_shared_first = true;
- }
- return sym;
}
+ 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
-
+Symbol* SymbolTable::lookup(const char* name, int len, TRAPS) {
+ 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;
}
-// 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);
+Symbol* SymbolTable::lookup(const Symbol* sym, int begin, int end, TRAPS) {
+ 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;
}
-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;
+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), _len(len), _str(key) {}
+ 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;
}
- for (int i=0; i<len; i++) {
- buffer[i] = name[i];
+};
+
+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;
}
- // Make sure there is no safepoint in the code above since name can't move.
- // We can't include the code in NoSafepointVerifier because of the
- // ResourceMark.
-
- // Grab SymbolTable_lock first.
- MutexLocker ml(SymbolTable_lock, THREAD);
-
- return the_table()->basic_add(index, (u1*)buffer, len, hashValue, true, THREAD);
+ Symbol* sym = stg.get_res_sym();
+ assert((sym == NULL) || sym->refcount() != 0, "found dead symbol");
+ return sym;
}
-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;
-}
-
-// 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<Symbol*, mtSymbol>* 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 +389,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,214 +405,243 @@
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);
+ 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;
+ }
+};
- 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; i<names_count; i++) {
- int index = table->hash_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);
+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 = 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");
+ }
+ return sym;
+}
+
+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;
+ };
+};
+
+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");
+ }
+}
+
+// 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;
+ };
+};
+
+void SymbolTable::dump(outputStream* st, bool verbose) {
+ if (!verbose) {
+ SymbolTable::the_table()->print_table_statistics(st, "SymbolTable");
+ } else {
+ 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");
}
}
}
-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;
- }
- // 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);
-}
-
-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");
-
- // 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");
- }
-
- // Cannot hit a safepoint in this function because the "this" pointer can move.
- NoSafepointVerifier nsv;
-
- // 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);
- } else {
- hashValue = hashValue_arg;
- index = index_arg;
- }
-
- // 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;
- }
-
- // 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");
-
- HashtableEntry<Symbol*, mtSymbol>* entry = new_entry(hashValue, sym);
- add_entry(index, entry);
- return sym;
-}
-
-// 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) {
+#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");
- // 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");
- }
- }
-
- // Cannot hit a safepoint in this function because the "this" pointer can move.
- NoSafepointVerifier nsv;
+ // add to the compact table
+ _writer->add(fixed_hash, sym);
- for (int i=0; i<names_count; i++) {
- // Check if the symbol table has been rehashed, if so, need to recalculate
- // the hash value.
- unsigned int hashValue;
- if (use_alternate_hashcode()) {
- hashValue = hash_symbol(names[i], lengths[i]);
- } else {
- hashValue = hashValues[i];
- }
- // Since look-up was done lock-free, we need to check if another
- // thread beat us in the race to insert the symbol.
- int index = hash_to_index(hashValue);
- Symbol* test = lookup(index, names[i], lengths[i], hashValue);
- if (test != NULL) {
- // A race occurred and another thread introduced the symbol, this one
- // will be dropped and collected. Use test instead.
- cp->symbol_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<Symbol*, mtSymbol>* entry = new_entry(hashValue, sym);
- add_entry(index, entry);
- cp->symbol_at_put(cp_indices[i], sym);
- }
+ return true;
}
- return true;
-}
+};
-
-void SymbolTable::verify() {
- for (int i = 0; i < the_table()->table_size(); ++i) {
- HashtableEntry<Symbol*, mtSymbol>* 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::dump(outputStream* st, bool verbose) {
- if (!verbose) {
- the_table()->print_table_statistics(st, "SymbolTable");
- } else {
- st->print_cr("VERSION: 1.0");
- for (int i = 0; i < the_table()->table_size(); ++i) {
- HashtableEntry<Symbol*, mtSymbol>* 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();
- }
- }
- }
+void SymbolTable::copy_shared_symbol_table(CompactSymbolTableWriter* writer) {
+ CopyToArchive copy(writer);
+ SymbolTable::the_table()->_local_table->do_scan(Thread::current(), copy);
}
void SymbolTable::write_to_archive() {
-#if INCLUDE_CDS
- _shared_table.reset();
+ _shared_table.reset();
- 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<Symbol*, mtSymbol>* 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);
- }
- }
+ int num_buckets = (int)(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);
- 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);
- assert(sym == _shared_table.lookup(name, hash, len), "sanity");
-#endif
+ // 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");
}
void SymbolTable::serialize(SerializeClosure* soc) {
-#if INCLUDE_CDS
_shared_table.set_type(CompactHashtable<Symbol*, char>::_symbol_table);
_shared_table.serialize(soc);
@@ -626,7 +649,201 @@
// Sanity. Make sure we don't use the shared table at dump time
_shared_table.reset();
}
-#endif
+}
+#endif //INCLUDE_CDS
+
+// Concurrent work
+void SymbolTable::grow(JavaThread* jt) {
+ SymbolTableHash::GrowTask gt(_local_table);
+ if (!gt.prepare(jt)) {
+ return;
+ }
+ log_trace(symboltable)("Started to grow");
+ {
+ TraceTime timer("Grow", TRACETIME_LOG(Debug, symboltable, perf));
+ while (gt.do_task(jt)) {
+ gt.pause(jt);
+ {
+ ThreadBlockInVM tbivm(jt);
+ }
+ gt.cont(jt);
+ }
+ }
+ gt.done(jt);
+ _current_size = table_size();
+ 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);
+ }
+
+ 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::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;
+}
+
+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 {
+ SymbolTable::the_table()->set_item_clean_count(counter.get_dead_count());
+ SymbolTable::the_table()->check_concurrent_work();
+ }
+}
+
+void SymbolTable::do_concurrent_work(JavaThread* jt) {
+ SymbolTable::the_table()->concurrent_work(jt);
+}
+
+// Rehash
+bool SymbolTable::do_rehash() {
+ if (!_local_table->is_safepoint_safe()) {
+ return false;
+ }
+
+ // 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;
+ }
+
+ // free old table
+ delete _local_table;
+ _local_table = new_table;
+
+ return true;
+}
+
+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;
+ }
+
+ // Already rehashed.
+ if (rehashed) {
+ log_warning(symboltable)("Rehashing already done, still long lists.");
+ trigger_concurrent_work();
+ _needs_rehashing = false;
+ return;
+ }
+
+ 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 +851,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<Symbol*, mtSymbol>* 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);
+ 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(" >=%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<Symbol*, mtSymbol>** p = the_table()->bucket_addr(i);
- HashtableEntry<Symbol*, mtSymbol>* 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<Symbol*, mtSymbol>*)HashtableEntry<Symbol*, mtSymbol>::make_ptr(*p);
- }
- tty->cr();
- }
- }
+ 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),
--- a/src/hotspot/share/classfile/symbolTable.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/classfile/symbolTable.hpp Tue Aug 14 18:42:14 2018 -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 T, class N> class CompactHashtable;
+class CompactSymbolTableWriter;
+class SerializeClosure;
-class SymbolTable : public RehashableHashtable<Symbol*, mtSymbol> {
+class SymbolTableConfig;
+typedef ConcurrentHashTable<Symbol*,
+ SymbolTableConfig, mtSymbol> SymbolTableHash;
+
+class SymbolTableCreateEntry;
+
+class SymbolTable : public CHeapObj<mtSymbol> {
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;
-
- // Set if one bucket is out of balance due to hash algorithm deficiency
- static bool _needs_rehashing;
- static bool _lookup_shared_first;
+ // Shared symbol table.
+ static CompactHashtable<Symbol*, char> _shared_table;
+ static volatile bool _lookup_shared_first;
+ static volatile bool _alt_hash;
// For statistics
- static int _symbols_removed;
- static int _symbols_counted;
+ 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
+ volatile bool _needs_rehashing;
+
+ volatile size_t _items_count;
+ volatile size_t _uncleaned_items_count;
- // shared symbol table.
- static CompactHashtable<Symbol*, char> _shared_table;
+ double get_load_factor();
+ double get_dead_factor();
+
+ void check_concurrent_work();
+ void trigger_concurrent_work();
- Symbol* allocate_symbol(const u1* name, int len, bool c_heap, TRAPS); // Assumes no characters larger than 0x7F
+ 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<Symbol*, mtSymbol>(SymbolTableSize, sizeof (HashtableEntry<Symbol*, mtSymbol>)) {}
-
- SymbolTable(HashtableBucket<mtSymbol>* t, int number_of_entries)
- : RehashableHashtable<Symbol*, mtSymbol>(SymbolTableSize, sizeof (HashtableEntry<Symbol*, mtSymbol>), 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,88 +167,45 @@
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();
+
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<mtSymbol>); }
-
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.
+ // adds new symbol if not found
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);
-
- // 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) {
- return new_symbol(name, (int)strlen(name), THREAD);
- }
- 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);
-
// Needed for preloading classes in signatures when compiling.
// Returns the symbol is already present in symbol table, otherwise
// NULL. NO ALLOCATION IS GUARANTEED!
@@ -246,27 +218,45 @@
return lookup_only_unicode(name, len, ignore_hash);
}
- // Histogram
- static void print_histogram() PRODUCT_RETURN;
- static void print() PRODUCT_RETURN;
+ // 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) {
+ return new_symbol(name, (int)strlen(name), THREAD);
+ }
+ 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);
- // Debugging
- static void verify();
- static void dump(outputStream* st, bool verbose=false);
- static void read(const char* filename, 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; }
+
+ // Heap dumper and CDS
+ static void symbols_do(SymbolClosure *cl);
// Sharing
- static void write_to_archive();
- static void serialize(SerializeClosure* soc);
- static u4 encode_shared(Symbol* sym);
- static Symbol* decode_shared(u4 offset);
+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);
- // 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; }
+ // Jcmd
+ static void dump(outputStream* st, bool verbose=false);
+ // Debugging
+ static void verify();
+ static void read(const char* filename, TRAPS);
+
+ // Histogram
+ static void print_histogram() PRODUCT_RETURN;
};
#endif // SHARE_VM_CLASSFILE_SYMBOLTABLE_HPP
--- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp Tue Aug 14 18:42:14 2018 -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"
@@ -3256,56 +3255,40 @@
undo_waste * HeapWordSize / K);
}
-class G1StringAndSymbolCleaningTask : public AbstractGangTask {
+class G1StringCleaningTask : public AbstractGangTask {
private:
BoolObjectClosure* _is_alive;
G1StringDedupUnlinkOrOopsDoClosure _dedup_closure;
OopStorage::ParState<false /* concurrent */, false /* const */> _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();
}
@@ -3314,18 +3297,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);
}
@@ -3333,9 +3309,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 {
@@ -3585,7 +3558,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;
@@ -3595,7 +3568,7 @@
G1ParallelCleaningTask(BoolObjectClosure* is_alive, uint num_workers, bool unloading_occurred) :
AbstractGangTask("Parallel Cleaning"),
_unloading_occurred(unloading_occurred),
- _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(),
_resolved_method_cleaning_task() {
@@ -3609,8 +3582,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();
@@ -3642,16 +3615,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 {
@@ -4045,7 +4016,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.
--- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp Tue Aug 14 18:42:14 2018 -0500
@@ -1324,9 +1324,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.
--- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp Tue Aug 14 18:42:14 2018 -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"
@@ -1578,8 +1577,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);
@@ -1673,16 +1672,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());
}
}
--- a/src/hotspot/share/gc/g1/g1FullCollector.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp Tue Aug 14 18:42:14 2018 -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);
--- a/src/hotspot/share/gc/z/zRootsIterator.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/gc/z/zRootsIterator.cpp Tue Aug 14 18:42:14 2018 -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);
--- a/src/hotspot/share/gc/z/zRootsIterator.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/gc/z/zRootsIterator.hpp Tue Aug 14 18:42:14 2018 -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<ZWeakRootsIterator, &ZWeakRootsIterator::do_jvmti_weak_export> _jvmti_weak_export;
ZSerialWeakOopsDo<ZWeakRootsIterator, &ZWeakRootsIterator::do_jfr_weak> _jfr_weak;
ZParallelWeakOopsDo<ZWeakRootsIterator, &ZWeakRootsIterator::do_vm_weak_handles> _vm_weak_handles;
ZParallelWeakOopsDo<ZWeakRootsIterator, &ZWeakRootsIterator::do_jni_weak_handles> _jni_weak_handles;
- ZParallelWeakOopsDo<ZWeakRootsIterator, &ZWeakRootsIterator::do_symbol_table> _symbol_table;
ZParallelWeakOopsDo<ZWeakRootsIterator, &ZWeakRootsIterator::do_string_table> _string_table;
public:
--- a/src/hotspot/share/gc/z/z_globals.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/gc/z/z_globals.hpp Tue Aug 14 18:42:14 2018 -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") \
\
--- a/src/hotspot/share/logging/logPrefix.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/logging/logPrefix.hpp Tue Aug 14 18:42:14 2018 -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)) \
--- a/src/hotspot/share/logging/logTag.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/logging/logTag.hpp Tue Aug 14 18:42:14 2018 -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) \
--- a/src/hotspot/share/memory/arena.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/memory/arena.hpp Tue Aug 14 18:42:14 2018 -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,
--- a/src/hotspot/share/oops/symbol.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/oops/symbol.cpp Tue Aug 14 18:42:14 2018 -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;)
--- a/src/hotspot/share/oops/symbol.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/oops/symbol.hpp Tue Aug 14 18:42:14 2018 -0500
@@ -256,7 +256,7 @@
// only for getting its vtable pointer.
Symbol() { }
- static int _total_count;
+ static size_t _total_count;
#endif
};
--- a/src/hotspot/share/runtime/serviceThread.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/runtime/serviceThread.cpp Tue Aug 14 18:42:14 2018 -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();
}
--- a/src/hotspot/share/runtime/vmStructs.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/runtime/vmStructs.cpp Tue Aug 14 18:42:14 2018 -0500
@@ -162,12 +162,8 @@
typedef HashtableEntry<intptr_t, mtInternal> IntptrHashtableEntry;
typedef Hashtable<intptr_t, mtInternal> IntptrHashtable;
-typedef Hashtable<Symbol*, mtSymbol> SymbolHashtable;
-typedef HashtableEntry<Symbol*, mtClass> SymbolHashtableEntry;
typedef Hashtable<InstanceKlass*, mtClass> KlassHashtable;
typedef HashtableEntry<InstanceKlass*, mtClass> KlassHashtableEntry;
-typedef CompactHashtable<Symbol*, char> SymbolCompactHashTable;
-typedef RehashableHashtable<Symbol*, mtSymbol> RehashableSymbolHashtable;
typedef PaddedEnd<ObjectMonitor> 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<mtInternal>) \
\
- /*********************************/ \
- /* SymbolTable, SystemDictionary */ \
- /*********************************/ \
+ /********************/ \
+ /* SystemDictionary */ \
+ /********************/ \
\
declare_toplevel_type(BasicHashtable<mtInternal>) \
declare_type(IntptrHashtable, BasicHashtable<mtInternal>) \
declare_toplevel_type(BasicHashtable<mtSymbol>) \
- declare_type(RehashableSymbolHashtable, BasicHashtable<mtSymbol>) \
- declare_type(SymbolTable, SymbolHashtable) \
declare_type(Dictionary, KlassHashtable) \
declare_toplevel_type(BasicHashtableEntry<mtInternal>) \
declare_type(IntptrHashtableEntry, BasicHashtableEntry<mtInternal>) \
@@ -1373,8 +1349,6 @@
declare_toplevel_type(Arena) \
declare_type(ResourceArea, Arena) \
\
- declare_toplevel_type(SymbolCompactHashTable) \
- \
/***********************************************************/ \
/* Thread hierarchy (needed for run-time type information) */ \
/***********************************************************/ \
--- a/src/hotspot/share/utilities/concurrentHashTable.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/utilities/concurrentHashTable.hpp Tue Aug 14 18:42:14 2018 -0500
@@ -309,7 +309,7 @@
// Insert which handles a number of cases.
template <typename LOOKUP_FUNC, typename VALUE_FUNC, typename CALLBACK_FUNC>
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 <typename LOOKUP_FUNC, typename VALUE_FUNC, typename CALLBACK_FUNC>
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 <typename LOOKUP_FUNC>
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
--- a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp Tue Aug 14 18:42:14 2018 -0500
@@ -540,6 +540,8 @@
inline void ConcurrentHashTable<VALUE, CONFIG, F>::
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 <typename LOOKUP_FUNC, typename VALUE_FUNC, typename CALLBACK_FUNC>
inline bool ConcurrentHashTable<VALUE, CONFIG, F>::
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;
}
--- a/src/hotspot/share/utilities/globalCounter.cpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/utilities/globalCounter.cpp Tue Aug 14 18:42:14 2018 -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(); ) {
--- a/src/hotspot/share/utilities/globalDefinitions.hpp Tue Aug 14 14:08:04 2018 -0700
+++ b/src/hotspot/share/utilities/globalDefinitions.hpp Tue Aug 14 18:42:14 2018 -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 size_t defaultStringTableSize = NOT_LP64(1024) LP64_ONLY(65536);
+const size_t minimumStringTableSize = 128;
-const int defaultSymbolTableSize = 20011;
-const int minimumSymbolTableSize = 1009;
+const size_t defaultSymbolTableSize = 32768; // 2^15
+const size_t minimumSymbolTableSize = 1024;
//----------------------------------------------------------------------------------------------------
--- a/test/hotspot/jtreg/gc/g1/TestStringSymbolTableStats.java Tue Aug 14 14:08:04 2018 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2013, 2016, 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 TestStringSymbolTableStats.java
- * @bug 8027476 8027455
- * @summary Ensure that the G1TraceStringSymbolTableScrubbing 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 TestStringSymbolTableStats {
- 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 and symbol table");
- output.shouldHaveExitValue(0);
- }
-
- static class SystemGCTest {
- public static void main(String [] args) {
- System.out.println("Calling System.gc()");
- System.gc();
- }
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/gc/g1/TestStringTableStats.java Tue Aug 14 18:42:14 2018 -0500
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2013, 2016, 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 Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/symboltable/ShortLivedSymbolCleanup.java Tue Aug 14 18:42:14 2018 -0500
@@ -0,0 +1,119 @@
+/*
+ * 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<size; i++) {
+ try {
+ Class.forName(String.format("%05d", i), false, null);
+ } catch (java.lang.ClassNotFoundException e) {}
+ }
+ }
+ }
+}
\ No newline at end of file