| 1 | /* |
| 2 | * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. |
| 8 | * |
| 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 12 | * version 2 for more details (a copy is included in the LICENSE file that |
| 13 | * accompanied this code). |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License version |
| 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 18 | * |
| 19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| 20 | * or visit www.oracle.com if you need additional information or have any |
| 21 | * questions. |
| 22 | * |
| 23 | */ |
| 24 | |
| 25 | #include "precompiled.hpp" |
| 26 | #include "classfile/altHashing.hpp" |
| 27 | #include "classfile/compactHashtable.hpp" |
| 28 | #include "classfile/javaClasses.inline.hpp" |
| 29 | #include "classfile/stringTable.hpp" |
| 30 | #include "classfile/systemDictionary.hpp" |
| 31 | #include "gc/shared/collectedHeap.hpp" |
| 32 | #include "gc/shared/oopStorage.inline.hpp" |
| 33 | #include "logging/log.hpp" |
| 34 | #include "logging/logStream.hpp" |
| 35 | #include "memory/allocation.inline.hpp" |
| 36 | #include "memory/filemap.hpp" |
| 37 | #include "memory/heapShared.inline.hpp" |
| 38 | #include "memory/resourceArea.hpp" |
| 39 | #include "memory/universe.hpp" |
| 40 | #include "oops/access.inline.hpp" |
| 41 | #include "oops/compressedOops.hpp" |
| 42 | #include "oops/oop.inline.hpp" |
| 43 | #include "oops/typeArrayOop.inline.hpp" |
| 44 | #include "oops/weakHandle.inline.hpp" |
| 45 | #include "runtime/atomic.hpp" |
| 46 | #include "runtime/handles.inline.hpp" |
| 47 | #include "runtime/mutexLocker.hpp" |
| 48 | #include "runtime/safepointVerifiers.hpp" |
| 49 | #include "runtime/timerTrace.hpp" |
| 50 | #include "runtime/interfaceSupport.inline.hpp" |
| 51 | #include "services/diagnosticCommand.hpp" |
| 52 | #include "utilities/concurrentHashTable.inline.hpp" |
| 53 | #include "utilities/concurrentHashTableTasks.inline.hpp" |
| 54 | #include "utilities/macros.hpp" |
| 55 | #include "utilities/utf8.hpp" |
| 56 | |
| 57 | // We prefer short chains of avg 2 |
| 58 | const double PREF_AVG_LIST_LEN = 2.0; |
| 59 | // 2^24 is max size |
| 60 | const size_t END_SIZE = 24; |
| 61 | // If a chain gets to 100 something might be wrong |
| 62 | const size_t REHASH_LEN = 100; |
| 63 | // If we have as many dead items as 50% of the number of bucket |
| 64 | const double CLEAN_DEAD_HIGH_WATER_MARK = 0.5; |
| 65 | |
| 66 | #if INCLUDE_CDS_JAVA_HEAP |
| 67 | inline oop read_string_from_compact_hashtable(address base_address, u4 offset) { |
| 68 | assert(sizeof(narrowOop) == sizeof(offset), "must be" ); |
| 69 | narrowOop v = (narrowOop)offset; |
| 70 | return HeapShared::decode_from_archive(v); |
| 71 | } |
| 72 | |
| 73 | static CompactHashtable< |
| 74 | const jchar*, oop, |
| 75 | read_string_from_compact_hashtable, |
| 76 | java_lang_String::equals |
| 77 | > _shared_table; |
| 78 | #endif |
| 79 | |
| 80 | // -------------------------------------------------------------------------- |
| 81 | |
| 82 | typedef ConcurrentHashTable<WeakHandle<vm_string_table_data>, |
| 83 | StringTableConfig, mtSymbol> StringTableHash; |
| 84 | static StringTableHash* _local_table = NULL; |
| 85 | |
| 86 | volatile bool StringTable::_has_work = false; |
| 87 | volatile bool StringTable::_needs_rehashing = false; |
| 88 | |
| 89 | volatile size_t StringTable::_uncleaned_items_count = 0; |
| 90 | OopStorage* StringTable::_weak_handles = NULL; |
| 91 | |
| 92 | static size_t _current_size = 0; |
| 93 | static volatile size_t _items_count = 0; |
| 94 | |
| 95 | volatile bool _alt_hash = false; |
| 96 | static juint murmur_seed = 0; |
| 97 | |
| 98 | uintx hash_string(const jchar* s, int len, bool useAlt) { |
| 99 | return useAlt ? |
| 100 | AltHashing::murmur3_32(murmur_seed, s, len) : |
| 101 | java_lang_String::hash_code(s, len); |
| 102 | } |
| 103 | |
| 104 | class StringTableConfig : public StringTableHash::BaseConfig { |
| 105 | private: |
| 106 | public: |
| 107 | static uintx get_hash(WeakHandle<vm_string_table_data> const& value, |
| 108 | bool* is_dead) { |
| 109 | EXCEPTION_MARK; |
| 110 | oop val_oop = value.peek(); |
| 111 | if (val_oop == NULL) { |
| 112 | *is_dead = true; |
| 113 | return 0; |
| 114 | } |
| 115 | *is_dead = false; |
| 116 | ResourceMark rm(THREAD); |
| 117 | // All String oops are hashed as unicode |
| 118 | int length; |
| 119 | jchar* chars = java_lang_String::as_unicode_string(val_oop, length, THREAD); |
| 120 | if (chars != NULL) { |
| 121 | return hash_string(chars, length, _alt_hash); |
| 122 | } |
| 123 | vm_exit_out_of_memory(length, OOM_MALLOC_ERROR, "get hash from oop" ); |
| 124 | return 0; |
| 125 | } |
| 126 | // We use default allocation/deallocation but counted |
| 127 | static void* allocate_node(size_t size, |
| 128 | WeakHandle<vm_string_table_data> const& value) { |
| 129 | StringTable::item_added(); |
| 130 | return StringTableHash::BaseConfig::allocate_node(size, value); |
| 131 | } |
| 132 | static void free_node(void* memory, |
| 133 | WeakHandle<vm_string_table_data> const& value) { |
| 134 | value.release(); |
| 135 | StringTableHash::BaseConfig::free_node(memory, value); |
| 136 | StringTable::item_removed(); |
| 137 | } |
| 138 | }; |
| 139 | |
| 140 | class StringTableLookupJchar : StackObj { |
| 141 | private: |
| 142 | Thread* _thread; |
| 143 | uintx _hash; |
| 144 | int _len; |
| 145 | const jchar* _str; |
| 146 | Handle _found; |
| 147 | |
| 148 | public: |
| 149 | StringTableLookupJchar(Thread* thread, uintx hash, const jchar* key, int len) |
| 150 | : _thread(thread), _hash(hash), _len(len), _str(key) { |
| 151 | } |
| 152 | uintx get_hash() const { |
| 153 | return _hash; |
| 154 | } |
| 155 | bool equals(WeakHandle<vm_string_table_data>* value, bool* is_dead) { |
| 156 | oop val_oop = value->peek(); |
| 157 | if (val_oop == NULL) { |
| 158 | // dead oop, mark this hash dead for cleaning |
| 159 | *is_dead = true; |
| 160 | return false; |
| 161 | } |
| 162 | bool equals = java_lang_String::equals(val_oop, _str, _len); |
| 163 | if (!equals) { |
| 164 | return false; |
| 165 | } |
| 166 | // Need to resolve weak handle and Handleize through possible safepoint. |
| 167 | _found = Handle(_thread, value->resolve()); |
| 168 | return true; |
| 169 | } |
| 170 | }; |
| 171 | |
| 172 | class StringTableLookupOop : public StackObj { |
| 173 | private: |
| 174 | Thread* _thread; |
| 175 | uintx _hash; |
| 176 | Handle _find; |
| 177 | Handle _found; // Might be a different oop with the same value that's already |
| 178 | // in the table, which is the point. |
| 179 | public: |
| 180 | StringTableLookupOop(Thread* thread, uintx hash, Handle handle) |
| 181 | : _thread(thread), _hash(hash), _find(handle) { } |
| 182 | |
| 183 | uintx get_hash() const { |
| 184 | return _hash; |
| 185 | } |
| 186 | |
| 187 | bool equals(WeakHandle<vm_string_table_data>* value, bool* is_dead) { |
| 188 | oop val_oop = value->peek(); |
| 189 | if (val_oop == NULL) { |
| 190 | // dead oop, mark this hash dead for cleaning |
| 191 | *is_dead = true; |
| 192 | return false; |
| 193 | } |
| 194 | bool equals = java_lang_String::equals(_find(), val_oop); |
| 195 | if (!equals) { |
| 196 | return false; |
| 197 | } |
| 198 | // Need to resolve weak handle and Handleize through possible safepoint. |
| 199 | _found = Handle(_thread, value->resolve()); |
| 200 | return true; |
| 201 | } |
| 202 | }; |
| 203 | |
| 204 | static size_t ceil_log2(size_t val) { |
| 205 | size_t ret; |
| 206 | for (ret = 1; ((size_t)1 << ret) < val; ++ret); |
| 207 | return ret; |
| 208 | } |
| 209 | |
| 210 | void StringTable::create_table() { |
| 211 | _weak_handles = new OopStorage("StringTable weak" , |
| 212 | StringTableWeakAlloc_lock, |
| 213 | StringTableWeakActive_lock); |
| 214 | size_t start_size_log_2 = ceil_log2(StringTableSize); |
| 215 | _current_size = ((size_t)1) << start_size_log_2; |
| 216 | log_trace(stringtable)("Start size: " SIZE_FORMAT " (" SIZE_FORMAT ")" , |
| 217 | _current_size, start_size_log_2); |
| 218 | _local_table = new StringTableHash(start_size_log_2, END_SIZE, REHASH_LEN); |
| 219 | } |
| 220 | |
| 221 | size_t StringTable::item_added() { |
| 222 | return Atomic::add((size_t)1, &_items_count); |
| 223 | } |
| 224 | |
| 225 | size_t StringTable::add_items_to_clean(size_t ndead) { |
| 226 | size_t total = Atomic::add((size_t)ndead, &_uncleaned_items_count); |
| 227 | log_trace(stringtable)( |
| 228 | "Uncleaned items:" SIZE_FORMAT " added: " SIZE_FORMAT " total:" SIZE_FORMAT, |
| 229 | _uncleaned_items_count, ndead, total); |
| 230 | return total; |
| 231 | } |
| 232 | |
| 233 | void StringTable::item_removed() { |
| 234 | Atomic::add((size_t)-1, &_items_count); |
| 235 | } |
| 236 | |
| 237 | double StringTable::get_load_factor() { |
| 238 | return (double)_items_count/_current_size; |
| 239 | } |
| 240 | |
| 241 | double StringTable::get_dead_factor() { |
| 242 | return (double)_uncleaned_items_count/_current_size; |
| 243 | } |
| 244 | |
| 245 | size_t StringTable::table_size() { |
| 246 | return ((size_t)1) << _local_table->get_size_log2(Thread::current()); |
| 247 | } |
| 248 | |
| 249 | void StringTable::trigger_concurrent_work() { |
| 250 | MutexLocker ml(Service_lock, Mutex::_no_safepoint_check_flag); |
| 251 | _has_work = true; |
| 252 | Service_lock->notify_all(); |
| 253 | } |
| 254 | |
| 255 | // Probing |
| 256 | oop StringTable::lookup(Symbol* symbol) { |
| 257 | ResourceMark rm; |
| 258 | int length; |
| 259 | jchar* chars = symbol->as_unicode(length); |
| 260 | return lookup(chars, length); |
| 261 | } |
| 262 | |
| 263 | oop StringTable::lookup(const jchar* name, int len) { |
| 264 | unsigned int hash = java_lang_String::hash_code(name, len); |
| 265 | oop string = lookup_shared(name, len, hash); |
| 266 | if (string != NULL) { |
| 267 | return string; |
| 268 | } |
| 269 | if (_alt_hash) { |
| 270 | hash = hash_string(name, len, true); |
| 271 | } |
| 272 | return do_lookup(name, len, hash); |
| 273 | } |
| 274 | |
| 275 | class StringTableGet : public StackObj { |
| 276 | Thread* _thread; |
| 277 | Handle _return; |
| 278 | public: |
| 279 | StringTableGet(Thread* thread) : _thread(thread) {} |
| 280 | void operator()(WeakHandle<vm_string_table_data>* val) { |
| 281 | oop result = val->resolve(); |
| 282 | assert(result != NULL, "Result should be reachable" ); |
| 283 | _return = Handle(_thread, result); |
| 284 | } |
| 285 | oop get_res_oop() { |
| 286 | return _return(); |
| 287 | } |
| 288 | }; |
| 289 | |
| 290 | oop StringTable::do_lookup(const jchar* name, int len, uintx hash) { |
| 291 | Thread* thread = Thread::current(); |
| 292 | StringTableLookupJchar lookup(thread, hash, name, len); |
| 293 | StringTableGet stg(thread); |
| 294 | bool rehash_warning; |
| 295 | _local_table->get(thread, lookup, stg, &rehash_warning); |
| 296 | update_needs_rehash(rehash_warning); |
| 297 | return stg.get_res_oop(); |
| 298 | } |
| 299 | |
| 300 | // Interning |
| 301 | oop StringTable::intern(Symbol* symbol, TRAPS) { |
| 302 | if (symbol == NULL) return NULL; |
| 303 | ResourceMark rm(THREAD); |
| 304 | int length; |
| 305 | jchar* chars = symbol->as_unicode(length); |
| 306 | Handle string; |
| 307 | oop result = intern(string, chars, length, CHECK_NULL); |
| 308 | return result; |
| 309 | } |
| 310 | |
| 311 | oop StringTable::intern(oop string, TRAPS) { |
| 312 | if (string == NULL) return NULL; |
| 313 | ResourceMark rm(THREAD); |
| 314 | int length; |
| 315 | Handle h_string (THREAD, string); |
| 316 | jchar* chars = java_lang_String::as_unicode_string(string, length, |
| 317 | CHECK_NULL); |
| 318 | oop result = intern(h_string, chars, length, CHECK_NULL); |
| 319 | return result; |
| 320 | } |
| 321 | |
| 322 | oop StringTable::intern(const char* utf8_string, TRAPS) { |
| 323 | if (utf8_string == NULL) return NULL; |
| 324 | ResourceMark rm(THREAD); |
| 325 | int length = UTF8::unicode_length(utf8_string); |
| 326 | jchar* chars = NEW_RESOURCE_ARRAY(jchar, length); |
| 327 | UTF8::convert_to_unicode(utf8_string, chars, length); |
| 328 | Handle string; |
| 329 | oop result = intern(string, chars, length, CHECK_NULL); |
| 330 | return result; |
| 331 | } |
| 332 | |
| 333 | oop StringTable::intern(Handle string_or_null_h, const jchar* name, int len, TRAPS) { |
| 334 | // shared table always uses java_lang_String::hash_code |
| 335 | unsigned int hash = java_lang_String::hash_code(name, len); |
| 336 | oop found_string = lookup_shared(name, len, hash); |
| 337 | if (found_string != NULL) { |
| 338 | return found_string; |
| 339 | } |
| 340 | if (_alt_hash) { |
| 341 | hash = hash_string(name, len, true); |
| 342 | } |
| 343 | found_string = do_lookup(name, len, hash); |
| 344 | if (found_string != NULL) { |
| 345 | return found_string; |
| 346 | } |
| 347 | return do_intern(string_or_null_h, name, len, hash, CHECK_NULL); |
| 348 | } |
| 349 | |
| 350 | oop StringTable::do_intern(Handle string_or_null_h, const jchar* name, |
| 351 | int len, uintx hash, TRAPS) { |
| 352 | HandleMark hm(THREAD); // cleanup strings created |
| 353 | Handle string_h; |
| 354 | |
| 355 | if (!string_or_null_h.is_null()) { |
| 356 | string_h = string_or_null_h; |
| 357 | } else { |
| 358 | string_h = java_lang_String::create_from_unicode(name, len, CHECK_NULL); |
| 359 | } |
| 360 | |
| 361 | // Deduplicate the string before it is interned. Note that we should never |
| 362 | // deduplicate a string after it has been interned. Doing so will counteract |
| 363 | // compiler optimizations done on e.g. interned string literals. |
| 364 | Universe::heap()->deduplicate_string(string_h()); |
| 365 | |
| 366 | assert(java_lang_String::equals(string_h(), name, len), |
| 367 | "string must be properly initialized" ); |
| 368 | assert(len == java_lang_String::length(string_h()), "Must be same length" ); |
| 369 | |
| 370 | StringTableLookupOop lookup(THREAD, hash, string_h); |
| 371 | StringTableGet stg(THREAD); |
| 372 | |
| 373 | bool rehash_warning; |
| 374 | do { |
| 375 | // Callers have already looked up the String using the jchar* name, so just go to add. |
| 376 | WeakHandle<vm_string_table_data> wh = WeakHandle<vm_string_table_data>::create(string_h); |
| 377 | // The hash table takes ownership of the WeakHandle, even if it's not inserted. |
| 378 | if (_local_table->insert(THREAD, lookup, wh, &rehash_warning)) { |
| 379 | update_needs_rehash(rehash_warning); |
| 380 | return wh.resolve(); |
| 381 | } |
| 382 | // In case another thread did a concurrent add, return value already in the table. |
| 383 | // This could fail if the String got gc'ed concurrently, so loop back until success. |
| 384 | if (_local_table->get(THREAD, lookup, stg, &rehash_warning)) { |
| 385 | update_needs_rehash(rehash_warning); |
| 386 | return stg.get_res_oop(); |
| 387 | } |
| 388 | } while(true); |
| 389 | } |
| 390 | |
| 391 | void StringTable::oops_do(OopClosure* f) { |
| 392 | assert(f != NULL, "No closure" ); |
| 393 | _weak_handles->oops_do(f); |
| 394 | } |
| 395 | |
| 396 | // Concurrent work |
| 397 | void StringTable::grow(JavaThread* jt) { |
| 398 | StringTableHash::GrowTask gt(_local_table); |
| 399 | if (!gt.prepare(jt)) { |
| 400 | return; |
| 401 | } |
| 402 | log_trace(stringtable)("Started to grow" ); |
| 403 | { |
| 404 | TraceTime timer("Grow" , TRACETIME_LOG(Debug, stringtable, perf)); |
| 405 | while (gt.do_task(jt)) { |
| 406 | gt.pause(jt); |
| 407 | { |
| 408 | ThreadBlockInVM tbivm(jt); |
| 409 | } |
| 410 | gt.cont(jt); |
| 411 | } |
| 412 | } |
| 413 | gt.done(jt); |
| 414 | _current_size = table_size(); |
| 415 | log_debug(stringtable)("Grown to size:" SIZE_FORMAT, _current_size); |
| 416 | } |
| 417 | |
| 418 | struct StringTableDoDelete : StackObj { |
| 419 | void operator()(WeakHandle<vm_string_table_data>* val) { |
| 420 | /* do nothing */ |
| 421 | } |
| 422 | }; |
| 423 | |
| 424 | struct StringTableDeleteCheck : StackObj { |
| 425 | long _count; |
| 426 | long _item; |
| 427 | StringTableDeleteCheck() : _count(0), _item(0) {} |
| 428 | bool operator()(WeakHandle<vm_string_table_data>* val) { |
| 429 | ++_item; |
| 430 | oop tmp = val->peek(); |
| 431 | if (tmp == NULL) { |
| 432 | ++_count; |
| 433 | return true; |
| 434 | } else { |
| 435 | return false; |
| 436 | } |
| 437 | } |
| 438 | }; |
| 439 | |
| 440 | void StringTable::clean_dead_entries(JavaThread* jt) { |
| 441 | StringTableHash::BulkDeleteTask bdt(_local_table); |
| 442 | if (!bdt.prepare(jt)) { |
| 443 | return; |
| 444 | } |
| 445 | |
| 446 | StringTableDeleteCheck stdc; |
| 447 | StringTableDoDelete stdd; |
| 448 | { |
| 449 | TraceTime timer("Clean" , TRACETIME_LOG(Debug, stringtable, perf)); |
| 450 | while(bdt.do_task(jt, stdc, stdd)) { |
| 451 | bdt.pause(jt); |
| 452 | { |
| 453 | ThreadBlockInVM tbivm(jt); |
| 454 | } |
| 455 | bdt.cont(jt); |
| 456 | } |
| 457 | bdt.done(jt); |
| 458 | } |
| 459 | log_debug(stringtable)("Cleaned %ld of %ld" , stdc._count, stdc._item); |
| 460 | } |
| 461 | |
| 462 | void StringTable::check_concurrent_work() { |
| 463 | if (_has_work) { |
| 464 | return; |
| 465 | } |
| 466 | |
| 467 | double load_factor = StringTable::get_load_factor(); |
| 468 | double dead_factor = StringTable::get_dead_factor(); |
| 469 | // We should clean/resize if we have more dead than alive, |
| 470 | // more items than preferred load factor or |
| 471 | // more dead items than water mark. |
| 472 | if ((dead_factor > load_factor) || |
| 473 | (load_factor > PREF_AVG_LIST_LEN) || |
| 474 | (dead_factor > CLEAN_DEAD_HIGH_WATER_MARK)) { |
| 475 | log_debug(stringtable)("Concurrent work triggered, live factor: %g dead factor: %g" , |
| 476 | load_factor, dead_factor); |
| 477 | trigger_concurrent_work(); |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | void StringTable::do_concurrent_work(JavaThread* jt) { |
| 482 | _has_work = false; |
| 483 | double load_factor = get_load_factor(); |
| 484 | log_debug(stringtable, perf)("Concurrent work, live factor: %g" , load_factor); |
| 485 | // We prefer growing, since that also removes dead items |
| 486 | if (load_factor > PREF_AVG_LIST_LEN && !_local_table->is_max_size_reached()) { |
| 487 | grow(jt); |
| 488 | } else { |
| 489 | clean_dead_entries(jt); |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | // Rehash |
| 494 | bool StringTable::do_rehash() { |
| 495 | if (!_local_table->is_safepoint_safe()) { |
| 496 | return false; |
| 497 | } |
| 498 | |
| 499 | // We use current size, not max size. |
| 500 | size_t new_size = _local_table->get_size_log2(Thread::current()); |
| 501 | StringTableHash* new_table = new StringTableHash(new_size, END_SIZE, REHASH_LEN); |
| 502 | // Use alt hash from now on |
| 503 | _alt_hash = true; |
| 504 | if (!_local_table->try_move_nodes_to(Thread::current(), new_table)) { |
| 505 | _alt_hash = false; |
| 506 | delete new_table; |
| 507 | return false; |
| 508 | } |
| 509 | |
| 510 | // free old table |
| 511 | delete _local_table; |
| 512 | _local_table = new_table; |
| 513 | |
| 514 | return true; |
| 515 | } |
| 516 | |
| 517 | void StringTable::rehash_table() { |
| 518 | static bool rehashed = false; |
| 519 | log_debug(stringtable)("Table imbalanced, rehashing called." ); |
| 520 | |
| 521 | // Grow instead of rehash. |
| 522 | if (get_load_factor() > PREF_AVG_LIST_LEN && |
| 523 | !_local_table->is_max_size_reached()) { |
| 524 | log_debug(stringtable)("Choosing growing over rehashing." ); |
| 525 | trigger_concurrent_work(); |
| 526 | _needs_rehashing = false; |
| 527 | return; |
| 528 | } |
| 529 | // Already rehashed. |
| 530 | if (rehashed) { |
| 531 | log_warning(stringtable)("Rehashing already done, still long lists." ); |
| 532 | trigger_concurrent_work(); |
| 533 | _needs_rehashing = false; |
| 534 | return; |
| 535 | } |
| 536 | |
| 537 | murmur_seed = AltHashing::compute_seed(); |
| 538 | { |
| 539 | if (do_rehash()) { |
| 540 | rehashed = true; |
| 541 | } else { |
| 542 | log_info(stringtable)("Resizes in progress rehashing skipped." ); |
| 543 | } |
| 544 | } |
| 545 | _needs_rehashing = false; |
| 546 | } |
| 547 | |
| 548 | // Statistics |
| 549 | static int literal_size(oop obj) { |
| 550 | // NOTE: this would over-count if (pre-JDK8) |
| 551 | // java_lang_Class::has_offset_field() is true and the String.value array is |
| 552 | // shared by several Strings. However, starting from JDK8, the String.value |
| 553 | // array is not shared anymore. |
| 554 | if (obj == NULL) { |
| 555 | return 0; |
| 556 | } else if (obj->klass() == SystemDictionary::String_klass()) { |
| 557 | return (obj->size() + java_lang_String::value(obj)->size()) * HeapWordSize; |
| 558 | } else { |
| 559 | return obj->size(); |
| 560 | } |
| 561 | } |
| 562 | |
| 563 | struct SizeFunc : StackObj { |
| 564 | size_t operator()(WeakHandle<vm_string_table_data>* val) { |
| 565 | oop s = val->peek(); |
| 566 | if (s == NULL) { |
| 567 | // Dead |
| 568 | return 0; |
| 569 | } |
| 570 | return literal_size(s); |
| 571 | }; |
| 572 | }; |
| 573 | |
| 574 | TableStatistics StringTable::get_table_statistics() { |
| 575 | static TableStatistics ts; |
| 576 | SizeFunc sz; |
| 577 | ts = _local_table->statistics_get(Thread::current(), sz, ts); |
| 578 | return ts; |
| 579 | } |
| 580 | |
| 581 | void StringTable::print_table_statistics(outputStream* st, |
| 582 | const char* table_name) { |
| 583 | SizeFunc sz; |
| 584 | _local_table->statistics_to(Thread::current(), sz, st, table_name); |
| 585 | } |
| 586 | |
| 587 | // Verification |
| 588 | class VerifyStrings : StackObj { |
| 589 | public: |
| 590 | bool operator()(WeakHandle<vm_string_table_data>* val) { |
| 591 | oop s = val->peek(); |
| 592 | if (s != NULL) { |
| 593 | assert(java_lang_String::length(s) >= 0, "Length on string must work." ); |
| 594 | } |
| 595 | return true; |
| 596 | }; |
| 597 | }; |
| 598 | |
| 599 | // This verification is part of Universe::verify() and needs to be quick. |
| 600 | void StringTable::verify() { |
| 601 | Thread* thr = Thread::current(); |
| 602 | VerifyStrings vs; |
| 603 | if (!_local_table->try_scan(thr, vs)) { |
| 604 | log_info(stringtable)("verify unavailable at this moment" ); |
| 605 | } |
| 606 | } |
| 607 | |
| 608 | // Verification and comp |
| 609 | class VerifyCompStrings : StackObj { |
| 610 | GrowableArray<oop>* _oops; |
| 611 | public: |
| 612 | size_t _errors; |
| 613 | VerifyCompStrings(GrowableArray<oop>* oops) : _oops(oops), _errors(0) {} |
| 614 | bool operator()(WeakHandle<vm_string_table_data>* val) { |
| 615 | oop s = val->resolve(); |
| 616 | if (s == NULL) { |
| 617 | return true; |
| 618 | } |
| 619 | int len = _oops->length(); |
| 620 | for (int i = 0; i < len; i++) { |
| 621 | bool eq = java_lang_String::equals(s, _oops->at(i)); |
| 622 | assert(!eq, "Duplicate strings" ); |
| 623 | if (eq) { |
| 624 | _errors++; |
| 625 | } |
| 626 | } |
| 627 | _oops->push(s); |
| 628 | return true; |
| 629 | }; |
| 630 | }; |
| 631 | |
| 632 | size_t StringTable::verify_and_compare_entries() { |
| 633 | Thread* thr = Thread::current(); |
| 634 | GrowableArray<oop>* oops = |
| 635 | new (ResourceObj::C_HEAP, mtInternal) |
| 636 | GrowableArray<oop>((int)_current_size, true); |
| 637 | |
| 638 | VerifyCompStrings vcs(oops); |
| 639 | if (!_local_table->try_scan(thr, vcs)) { |
| 640 | log_info(stringtable)("verify unavailable at this moment" ); |
| 641 | } |
| 642 | delete oops; |
| 643 | return vcs._errors; |
| 644 | } |
| 645 | |
| 646 | // Dumping |
| 647 | class PrintString : StackObj { |
| 648 | Thread* _thr; |
| 649 | outputStream* _st; |
| 650 | public: |
| 651 | PrintString(Thread* thr, outputStream* st) : _thr(thr), _st(st) {} |
| 652 | bool operator()(WeakHandle<vm_string_table_data>* val) { |
| 653 | oop s = val->peek(); |
| 654 | if (s == NULL) { |
| 655 | return true; |
| 656 | } |
| 657 | typeArrayOop value = java_lang_String::value_no_keepalive(s); |
| 658 | int length = java_lang_String::length(s); |
| 659 | bool is_latin1 = java_lang_String::is_latin1(s); |
| 660 | |
| 661 | if (length <= 0) { |
| 662 | _st->print("%d: " , length); |
| 663 | } else { |
| 664 | ResourceMark rm(_thr); |
| 665 | int utf8_length = length; |
| 666 | char* utf8_string; |
| 667 | |
| 668 | if (!is_latin1) { |
| 669 | jchar* chars = value->char_at_addr(0); |
| 670 | utf8_string = UNICODE::as_utf8(chars, utf8_length); |
| 671 | } else { |
| 672 | jbyte* bytes = value->byte_at_addr(0); |
| 673 | utf8_string = UNICODE::as_utf8(bytes, utf8_length); |
| 674 | } |
| 675 | |
| 676 | _st->print("%d: " , utf8_length); |
| 677 | HashtableTextDump::put_utf8(_st, utf8_string, utf8_length); |
| 678 | } |
| 679 | _st->cr(); |
| 680 | return true; |
| 681 | }; |
| 682 | }; |
| 683 | |
| 684 | void StringTable::dump(outputStream* st, bool verbose) { |
| 685 | if (!verbose) { |
| 686 | print_table_statistics(st, "StringTable" ); |
| 687 | } else { |
| 688 | Thread* thr = Thread::current(); |
| 689 | ResourceMark rm(thr); |
| 690 | st->print_cr("VERSION: 1.1" ); |
| 691 | PrintString ps(thr, st); |
| 692 | if (!_local_table->try_scan(thr, ps)) { |
| 693 | st->print_cr("dump unavailable at this moment" ); |
| 694 | } |
| 695 | } |
| 696 | } |
| 697 | |
| 698 | // Utility for dumping strings |
| 699 | StringtableDCmd::StringtableDCmd(outputStream* output, bool heap) : |
| 700 | DCmdWithParser(output, heap), |
| 701 | _verbose("-verbose" , "Dump the content of each string in the table" , |
| 702 | "BOOLEAN" , false, "false" ) { |
| 703 | _dcmdparser.add_dcmd_option(&_verbose); |
| 704 | } |
| 705 | |
| 706 | void StringtableDCmd::execute(DCmdSource source, TRAPS) { |
| 707 | VM_DumpHashtable dumper(output(), VM_DumpHashtable::DumpStrings, |
| 708 | _verbose.value()); |
| 709 | VMThread::execute(&dumper); |
| 710 | } |
| 711 | |
| 712 | int StringtableDCmd::num_arguments() { |
| 713 | ResourceMark rm; |
| 714 | StringtableDCmd* dcmd = new StringtableDCmd(NULL, false); |
| 715 | if (dcmd != NULL) { |
| 716 | DCmdMark mark(dcmd); |
| 717 | return dcmd->_dcmdparser.num_arguments(); |
| 718 | } else { |
| 719 | return 0; |
| 720 | } |
| 721 | } |
| 722 | |
| 723 | // Sharing |
| 724 | #if INCLUDE_CDS_JAVA_HEAP |
| 725 | oop StringTable::lookup_shared(const jchar* name, int len, unsigned int hash) { |
| 726 | assert(hash == java_lang_String::hash_code(name, len), |
| 727 | "hash must be computed using java_lang_String::hash_code" ); |
| 728 | return _shared_table.lookup(name, hash, len); |
| 729 | } |
| 730 | |
| 731 | oop StringTable::create_archived_string(oop s, Thread* THREAD) { |
| 732 | assert(DumpSharedSpaces, "this function is only used with -Xshare:dump" ); |
| 733 | |
| 734 | if (HeapShared::is_archived_object(s)) { |
| 735 | return s; |
| 736 | } |
| 737 | |
| 738 | oop new_s = NULL; |
| 739 | typeArrayOop v = java_lang_String::value_no_keepalive(s); |
| 740 | typeArrayOop new_v = (typeArrayOop)HeapShared::archive_heap_object(v, THREAD); |
| 741 | if (new_v == NULL) { |
| 742 | return NULL; |
| 743 | } |
| 744 | new_s = HeapShared::archive_heap_object(s, THREAD); |
| 745 | if (new_s == NULL) { |
| 746 | return NULL; |
| 747 | } |
| 748 | |
| 749 | // adjust the pointer to the 'value' field in the new String oop |
| 750 | java_lang_String::set_value_raw(new_s, new_v); |
| 751 | return new_s; |
| 752 | } |
| 753 | |
| 754 | struct CopyToArchive : StackObj { |
| 755 | CompactHashtableWriter* _writer; |
| 756 | CopyToArchive(CompactHashtableWriter* writer) : _writer(writer) {} |
| 757 | bool operator()(WeakHandle<vm_string_table_data>* val) { |
| 758 | oop s = val->peek(); |
| 759 | if (s == NULL) { |
| 760 | return true; |
| 761 | } |
| 762 | unsigned int hash = java_lang_String::hash_code(s); |
| 763 | oop new_s = StringTable::create_archived_string(s, Thread::current()); |
| 764 | if (new_s == NULL) { |
| 765 | return true; |
| 766 | } |
| 767 | |
| 768 | val->replace(new_s); |
| 769 | // add to the compact table |
| 770 | _writer->add(hash, CompressedOops::encode(new_s)); |
| 771 | return true; |
| 772 | } |
| 773 | }; |
| 774 | |
| 775 | void StringTable::copy_shared_string_table(CompactHashtableWriter* writer) { |
| 776 | assert(HeapShared::is_heap_object_archiving_allowed(), "must be" ); |
| 777 | |
| 778 | CopyToArchive copy(writer); |
| 779 | _local_table->do_safepoint_scan(copy); |
| 780 | } |
| 781 | |
| 782 | void StringTable::write_to_archive() { |
| 783 | assert(HeapShared::is_heap_object_archiving_allowed(), "must be" ); |
| 784 | |
| 785 | _shared_table.reset(); |
| 786 | CompactHashtableWriter writer(_items_count, &MetaspaceShared::stats()->string); |
| 787 | |
| 788 | // Copy the interned strings into the "string space" within the java heap |
| 789 | copy_shared_string_table(&writer); |
| 790 | writer.dump(&_shared_table, "string" ); |
| 791 | } |
| 792 | |
| 793 | void StringTable::(SerializeClosure* soc) { |
| 794 | _shared_table.serialize_header(soc); |
| 795 | |
| 796 | if (soc->writing()) { |
| 797 | // Sanity. Make sure we don't use the shared table at dump time |
| 798 | _shared_table.reset(); |
| 799 | } else if (!HeapShared::closed_archive_heap_region_mapped()) { |
| 800 | _shared_table.reset(); |
| 801 | } |
| 802 | } |
| 803 | |
| 804 | class SharedStringIterator { |
| 805 | OopClosure* _oop_closure; |
| 806 | public: |
| 807 | SharedStringIterator(OopClosure* f) : _oop_closure(f) {} |
| 808 | void do_value(oop string) { |
| 809 | _oop_closure->do_oop(&string); |
| 810 | } |
| 811 | }; |
| 812 | |
| 813 | void StringTable::shared_oops_do(OopClosure* f) { |
| 814 | SharedStringIterator iter(f); |
| 815 | _shared_table.iterate(&iter); |
| 816 | } |
| 817 | #endif //INCLUDE_CDS_JAVA_HEAP |
| 818 | |