--- old/src/share/vm/classfile/stringTable.cpp 2014-12-03 19:32:43.613280927 -0500 +++ new/src/share/vm/classfile/stringTable.cpp 2014-12-03 19:32:42.689228681 -0500 @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "classfile/altHashing.hpp" +#include "classfile/compactHashtable.hpp" #include "classfile/javaClasses.hpp" #include "classfile/stringTable.hpp" #include "classfile/systemDictionary.hpp" @@ -378,8 +379,36 @@ } } -void StringTable::dump(outputStream* st) { - the_table()->dump_table(st, "StringTable"); +void StringTable::dump(outputStream* st, bool verbose) { + if (!verbose) { + the_table()->dump_table(st, "StringTable"); + } else { + Thread* THREAD = Thread::current(); + st->print_cr("VERSION: 1.1"); + for (int i = 0; i < the_table()->table_size(); ++i) { + HashtableEntry* p = the_table()->bucket(i); + for ( ; p != NULL; p = p->next()) { + oop s = p->literal(); + typeArrayOop value = java_lang_String::value(s); + int offset = java_lang_String::offset(s); + int length = java_lang_String::length(s); + + if (length <= 0) { + st->print("%d: ", length); + } else { + ResourceMark rm(THREAD); + jchar* chars = (jchar*)value->char_at_addr(offset); + int utf8_length = UNICODE::utf8_length(chars, length); + char* utf8_string = NEW_RESOURCE_ARRAY(char, utf8_length + 1); + UNICODE::convert_to_utf8(chars, length, utf8_string); + + st->print("%d: ", utf8_length); + HashtableTextDump::put_utf8(st, utf8_string, utf8_length); + } + st->cr(); + } + } + } } StringTable::VerifyRetTypes StringTable::compare_entries( @@ -557,3 +586,28 @@ _needs_rehashing = false; _the_table = new_table; } + +// Utility for dumping strings +StringtableDCmd::StringtableDCmd(outputStream* output, bool heap) : + DCmdWithParser(output, heap), + _verbose("-verbose", "Dump the content of each string in the table", + "BOOLEAN", false, "false") { + _dcmdparser.add_dcmd_option(&_verbose); +} + +void StringtableDCmd::execute(DCmdSource source, TRAPS) { + VM_DumpHashtable dumper(output(), VM_DumpHashtable::DumpStrings, + _verbose.value()); + VMThread::execute(&dumper); +} + +int StringtableDCmd::num_arguments() { + ResourceMark rm; + StringtableDCmd* dcmd = new StringtableDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } else { + return 0; + } +} --- old/src/share/vm/classfile/stringTable.hpp 2014-12-03 19:32:46.609450332 -0500 +++ new/src/share/vm/classfile/stringTable.hpp 2014-12-03 19:32:45.689398305 -0500 @@ -118,7 +118,7 @@ // Debugging static void verify(); - static void dump(outputStream* st); + static void dump(outputStream* st, bool verbose=false); enum VerifyMesgModes { _verify_quietly = 0, --- old/src/share/vm/classfile/symbolTable.cpp 2014-12-03 19:32:49.493613401 -0500 +++ new/src/share/vm/classfile/symbolTable.cpp 2014-12-03 19:32:48.581561834 -0500 @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "classfile/altHashing.hpp" +#include "classfile/compactHashtable.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" @@ -47,6 +48,9 @@ // Static arena for symbols that are not deallocated Arena* SymbolTable::_arena = NULL; bool SymbolTable::_needs_rehashing = false; +bool SymbolTable::_lookup_shared_first = false; + +CompactHashtable SymbolTable::_shared_table; Symbol* SymbolTable::allocate_symbol(const u1* name, int len, bool c_heap, TRAPS) { assert (len <= Symbol::max_length(), "should be checked by caller"); @@ -186,8 +190,8 @@ // Lookup a symbol in a bucket. -Symbol* SymbolTable::lookup(int index, const char* name, - int len, unsigned int hash) { +Symbol* SymbolTable::lookup_dynamic(int index, const char* name, + int len, unsigned int hash) { int count = 0; for (HashtableEntry* e = bucket(index); e != NULL; e = e->next()) { count++; // count all entries in this bucket, not just ones with same hash @@ -207,6 +211,34 @@ return NULL; } +Symbol* SymbolTable::lookup_shared(const char* name, + int len, unsigned int hash) { + return _shared_table.lookup(name, hash, len); +} + +Symbol* SymbolTable::lookup(int index, 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; + } + _lookup_shared_first = false; + return lookup_dynamic(index, name, len, hash); + } else { + sym = lookup_dynamic(index, name, len, hash); + if (sym != NULL) { + return sym; + } + sym = lookup_shared(name, len, hash); + if (sym != NULL) { + _lookup_shared_first = true; + } + return sym; + } +} + // Pick hashing algorithm. unsigned int SymbolTable::hash_symbol(const char* s, int len) { return use_alternate_hashcode() ? @@ -483,10 +515,56 @@ } } -void SymbolTable::dump(outputStream* st) { - the_table()->dump_table(st, "SymbolTable"); +void SymbolTable::dump(outputStream* st, bool verbose) { + if (!verbose) { + the_table()->dump_table(st, "SymbolTable"); + } else { + st->print_cr("VERSION: 1.0"); + for (int i = 0; i < the_table()->table_size(); ++i) { + HashtableEntry* p = the_table()->bucket(i); + for ( ; p != NULL; p = p->next()) { + Symbol* s = (Symbol*)(p->literal()); + const char* utf8_string = (const char*)s->bytes(); + int utf8_length = s->utf8_length(); + st->print("%d %d: ", utf8_length, s->refcount()); + HashtableTextDump::put_utf8(st, utf8_string, utf8_length); + st->cr(); + } + } + } } +bool SymbolTable::copy_compact_table(char** top, char*end) { +#if INCLUDE_CDS + CompactHashtableWriter ch_table("symbol", the_table()->number_of_entries(), + &MetaspaceShared::stats()->symbol); + if (*top + ch_table.get_required_bytes() > end) { + // not enough space left + return false; + } + + for (int i = 0; i < the_table()->table_size(); ++i) { + HashtableEntry* p = the_table()->bucket(i); + for ( ; p != NULL; p = p->next()) { + Symbol* s = (Symbol*)(p->literal()); + unsigned int fixed_hash = hash_symbol((char*)s->bytes(), s->utf8_length()); + assert(fixed_hash == p->hash(), "must not rehash during dumping"); + ch_table.add(fixed_hash, s); + } + } + + char* old_top = *top; + ch_table.dump(top, end); + + *top = (char*)align_pointer_up(*top, sizeof(void*)); +#endif + return true; +} + +const char* SymbolTable::init_shared_table(const char* buffer) { + const char* end = _shared_table.init(buffer); + return (const char*)align_pointer_up(end, sizeof(void*)); +} //--------------------------------------------------------------------------- // Non-product code @@ -574,3 +652,29 @@ } } #endif // PRODUCT + + +// Utility for dumping symbols +SymboltableDCmd::SymboltableDCmd(outputStream* output, bool heap) : + DCmdWithParser(output, heap), + _verbose("-verbose", "Dump the content of each symbol in the table", + "BOOLEAN", false, "false") { + _dcmdparser.add_dcmd_option(&_verbose); +} + +void SymboltableDCmd::execute(DCmdSource source, TRAPS) { + VM_DumpHashtable dumper(output(), VM_DumpHashtable::DumpSymbols, + _verbose.value()); + VMThread::execute(&dumper); +} + +int SymboltableDCmd::num_arguments() { + ResourceMark rm; + SymboltableDCmd* dcmd = new SymboltableDCmd(NULL, false); + if (dcmd != NULL) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } else { + return 0; + } +} --- old/src/share/vm/classfile/symbolTable.hpp 2014-12-03 19:32:52.333773990 -0500 +++ new/src/share/vm/classfile/symbolTable.hpp 2014-12-03 19:32:51.409721738 -0500 @@ -73,6 +73,8 @@ operator Symbol*() { return _temp; } }; +template class CompactHashtable; + class SymbolTable : public RehashableHashtable { friend class VMStructs; friend class ClassFileParser; @@ -83,11 +85,15 @@ // Set if one bucket is out of balance due to hash algorithm deficiency static bool _needs_rehashing; + static bool _lookup_shared_first; // For statistics static int _symbols_removed; static int _symbols_counted; + // shared symbol table. + static CompactHashtable _shared_table; + Symbol* allocate_symbol(const u1* name, int len, bool c_heap, TRAPS); // Assumes no characters larger than 0x7F // Adding elements @@ -106,6 +112,8 @@ add(loader_data, cp, names_count, name, lengths, cp_indices, hashValues, THREAD); } + 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() @@ -144,20 +152,6 @@ initialize_symbols(symbol_alloc_arena_size); } - static void create_table(HashtableBucket* t, int length, - int number_of_entries) { - assert(_the_table == NULL, "One symbol table allowed."); - - // If CDS archive used a different symbol table size, use that size instead - // which is better than giving an error. - SymbolTableSize = length/bucket_size(); - - _the_table = new SymbolTable(t, number_of_entries); - // if CDS give symbol table a default arena size since most symbols - // are already allocated in the shared misc section. - initialize_symbols(); - } - static unsigned int hash_symbol(const char* s, int len); static Symbol* lookup(const char* name, int len, TRAPS); @@ -230,18 +224,12 @@ // Debugging static void verify(); - static void dump(outputStream* st); + static void dump(outputStream* st, bool verbose=false); + static void read(const char* filename, TRAPS); // Sharing - static void copy_buckets(char** top, char*end) { - the_table()->Hashtable::copy_buckets(top, end); - } - static void copy_table(char** top, char*end) { - the_table()->Hashtable::copy_table(top, end); - } - static void reverse(void* boundary = NULL) { - the_table()->Hashtable::reverse(boundary); - } + static bool copy_compact_table(char** top, char* end); + static const char* init_shared_table(const char* buffer); // Rehash the symbol table if it gets out of balance static void rehash_table(); --- old/src/share/vm/memory/metaspaceShared.cpp 2014-12-03 19:32:55.157933674 -0500 +++ new/src/share/vm/memory/metaspaceShared.cpp 2014-12-03 19:32:54.241881876 -0500 @@ -48,6 +48,8 @@ ReservedSpace* MetaspaceShared::_shared_rs = NULL; +MetaspaceSharedStats MetaspaceShared::_stats; + bool MetaspaceShared::_link_classes_made_progress; bool MetaspaceShared::_check_classes_made_progress; bool MetaspaceShared::_has_error_classes; @@ -259,7 +261,7 @@ #define SHAREDSPACE_OBJ_TYPES_DO(f) \ METASPACE_OBJ_TYPES_DO(f) \ f(SymbolHashentry) \ - f(SymbolBuckets) \ + f(SymbolBucket) \ f(Other) #define SHAREDSPACE_OBJ_TYPE_DECLARE(name) name ## Type, @@ -315,18 +317,16 @@ int other_bytes = md_all + mc_all; // Calculate size of data that was not allocated by Metaspace::allocate() - int symbol_count = _counts[RO][MetaspaceObj::SymbolType]; - int symhash_bytes = symbol_count * sizeof (HashtableEntry); - int symbuck_count = SymbolTable::the_table()->table_size(); - int symbuck_bytes = symbuck_count * sizeof(HashtableBucket); - - _counts[RW][SymbolHashentryType] = symbol_count; - _bytes [RW][SymbolHashentryType] = symhash_bytes; - other_bytes -= symhash_bytes; - - _counts[RW][SymbolBucketsType] = symbuck_count; - _bytes [RW][SymbolBucketsType] = symbuck_bytes; - other_bytes -= symbuck_bytes; + MetaspaceSharedStats *stats = MetaspaceShared::stats(); + + // symbols + _counts[RW][SymbolHashentryType] = stats->symbol.hashentry_count; + _bytes [RW][SymbolHashentryType] = stats->symbol.hashentry_bytes; + other_bytes -= stats->symbol.hashentry_bytes; + + _counts[RW][SymbolBucketType] = stats->symbol.bucket_count; + _bytes [RW][SymbolBucketType] = stats->symbol.bucket_bytes; + other_bytes -= stats->symbol.bucket_bytes; // TODO: count things like dictionary, vtable, etc _bytes[RW][OtherType] = other_bytes; @@ -424,6 +424,17 @@ VMOp_Type type() const { return VMOp_PopulateDumpSharedSpace; } void doit(); // outline because gdb sucks + +private: + void handle_failure(bool success, const char* what) { + if (!success) { + tty->print_cr("Insufficient shared space: please increase -XX:%s= to increase its size", what); + exit(1); + } + } + void handle_misc_data_space_failure(bool success) { + handle_failure(success, "SharedMiscDataSize"); + } }; // class VM_PopulateDumpSharedSpace @@ -517,9 +528,8 @@ // buckets first [read-write], then copy the linked lists of entries // [read-only]. - SymbolTable::reverse(md_top); NOT_PRODUCT(SymbolTable::verify()); - SymbolTable::copy_buckets(&md_top, md_end); + handle_misc_data_space_failure(SymbolTable::copy_compact_table(&md_top, md_end)); SystemDictionary::reverse(); SystemDictionary::copy_buckets(&md_top, md_end); @@ -528,7 +538,6 @@ ClassLoader::copy_package_info_buckets(&md_top, md_end); ClassLoader::verify(); - SymbolTable::copy_table(&md_top, md_end); SystemDictionary::copy_table(&md_top, md_end); ClassLoader::verify(); ClassLoader::copy_package_info_table(&md_top, md_end); @@ -1000,17 +1009,12 @@ buffer += sizeof(intptr_t); buffer += vtable_size; - // Create the symbol table using the bucket array at this spot in the - // misc data space. Since the symbol table is often modified, this - // region (of mapped pages) will be copy-on-write. + // Create the shared symbol table using the bucket array at this spot in the + // misc data space. (Todo: move this to read-only space. Currently + // this is mapped copy-on-write but will never be written into). - int symbolTableLen = *(intptr_t*)buffer; - buffer += sizeof(intptr_t); - int number_of_entries = *(intptr_t*)buffer; - buffer += sizeof(intptr_t); - SymbolTable::create_table((HashtableBucket*)buffer, symbolTableLen, - number_of_entries); - buffer += symbolTableLen; + buffer = (char*)SymbolTable::init_shared_table(buffer); + SymbolTable::create_table(); // Create the shared dictionary using the bucket array at this spot in // the misc data space. Since the shared dictionary table is never @@ -1019,7 +1023,7 @@ int sharedDictionaryLen = *(intptr_t*)buffer; buffer += sizeof(intptr_t); - number_of_entries = *(intptr_t*)buffer; + int number_of_entries = *(intptr_t*)buffer; buffer += sizeof(intptr_t); SystemDictionary::set_shared_dictionary((HashtableBucket*)buffer, sharedDictionaryLen, @@ -1041,18 +1045,10 @@ ClassLoader::verify(); // The following data in the shared misc data region are the linked - // list elements (HashtableEntry objects) for the symbol table, string - // table, and shared dictionary. The heap objects referred to by the - // symbol table, string table, and shared dictionary are permanent and - // unmovable. Since new entries added to the string and symbol tables - // are always added at the beginning of the linked lists, THESE LINKED - // LIST ELEMENTS ARE READ-ONLY. - - int len = *(intptr_t*)buffer; // skip over symbol table entries - buffer += sizeof(intptr_t); - buffer += len; + // list elements (HashtableEntry objects) for the shared dictionary + // and package info table. - len = *(intptr_t*)buffer; // skip over shared dictionary entries + int len = *(intptr_t*)buffer; // skip over shared dictionary entries buffer += sizeof(intptr_t); buffer += len; --- old/src/share/vm/memory/metaspaceShared.hpp 2014-12-03 19:32:58.074098559 -0500 +++ new/src/share/vm/memory/metaspaceShared.hpp 2014-12-03 19:32:57.158046761 -0500 @@ -24,6 +24,7 @@ #ifndef SHARE_VM_MEMORY_METASPACE_SHARED_HPP #define SHARE_VM_MEMORY_METASPACE_SHARED_HPP +#include "classfile/compactHashtable.hpp" #include "memory/allocation.hpp" #include "memory/memRegion.hpp" #include "runtime/virtualspace.hpp" @@ -45,12 +46,21 @@ class FileMapInfo; +class MetaspaceSharedStats VALUE_OBJ_CLASS_SPEC { +public: + MetaspaceSharedStats() { + memset(this, 0, sizeof(*this)); + } + CompactHashtableStats symbol; +}; + // Class Data Sharing Support class MetaspaceShared : AllStatic { // CDS support static ReservedSpace* _shared_rs; static int _max_alignment; + static MetaspaceSharedStats _stats; static bool _link_classes_made_progress; static bool _check_classes_made_progress; static bool _has_error_classes; @@ -118,6 +128,10 @@ char** mc_top, char* mc_end); static void serialize(SerializeClosure* sc); + static MetaspaceSharedStats* stats() { + return &_stats; + } + // JVM/TI RedefineClasses() support: // Remap the shared readonly space to shared readwrite, private if // sharing is enabled. Simply returns true if sharing is not enabled --- old/src/share/vm/memory/universe.cpp 2014-12-03 19:33:00.878257107 -0500 +++ new/src/share/vm/memory/universe.cpp 2014-12-03 19:32:59.962205317 -0500 @@ -1169,9 +1169,7 @@ MemoryService::set_universe_heap(Universe::_collectedHeap); #if INCLUDE_CDS - if (UseSharedSpaces) { - SharedClassUtil::initialize(CHECK_false); - } + SharedClassUtil::initialize(CHECK_false); #endif return true; } --- old/src/share/vm/runtime/globals.hpp 2014-12-03 19:33:03.730418388 -0500 +++ new/src/share/vm/runtime/globals.hpp 2014-12-03 19:33:02.790365234 -0500 @@ -3795,6 +3795,9 @@ NOT_LP64(LINUX_ONLY(2*G) NOT_LINUX(0)), \ "Address to allocate shared memory region for class data") \ \ + product(uintx, SharedSymbolTableBucketSize, 4, \ + "Average number of symbols per bucket in shared table") \ + \ diagnostic(bool, IgnoreUnverifiableClassesDuringDump, false, \ "Do not quit -Xshare:dump even if we encounter unverifiable " \ "classes. Just exclude them from the shared dictionary.") \ --- old/src/share/vm/runtime/vm_operations.hpp 2014-12-03 19:33:12.050888867 -0500 +++ new/src/share/vm/runtime/vm_operations.hpp 2014-12-03 19:33:11.042831866 -0500 @@ -100,6 +100,7 @@ template(RotateGCLog) \ template(WhiteBoxOperation) \ template(ClassLoaderStatsOperation) \ + template(DumpHashtable) \ template(MarkActiveNMethods) \ template(PrintCompileQueue) \ template(PrintCodeList) \ --- old/src/share/vm/services/diagnosticCommand.cpp 2014-12-03 19:33:14.859047657 -0500 +++ new/src/share/vm/services/diagnosticCommand.cpp 2014-12-03 19:33:13.938995632 -0500 @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "classfile/classLoaderStats.hpp" +#include "classfile/compactHashtable.hpp" #include "gc_implementation/shared/vmGCOperations.hpp" #include "runtime/javaCalls.hpp" #include "runtime/os.hpp" @@ -56,6 +57,8 @@ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(DCmd_Source_Internal | DCmd_Source_AttachAPI, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #endif // INCLUDE_SERVICES DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); --- /dev/null 2014-09-29 03:28:39.162223999 -0400 +++ new/src/share/vm/classfile/compactHashtable.cpp 2014-12-03 19:33:16.747154424 -0500 @@ -0,0 +1,425 @@ +/* + * Copyright (c) 1997, 2014, 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. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.hpp" +#include "memory/metaspaceShared.hpp" +#include "utilities/numberSeq.hpp" +#include + +///////////////////////////////////////////////////// +// +// The compact hash table writer implementations +// +CompactHashtableWriter::CompactHashtableWriter(const char* table_name, + int num_entries, + CompactHashtableStats* stats) { + assert(DumpSharedSpaces, "dump-time only"); + _table_name = table_name; + _num_entries = num_entries; + _num_buckets = number_of_buckets(_num_entries); + _buckets = NEW_C_HEAP_ARRAY(Entry*, _num_buckets, mtSymbol); + memset(_buckets, 0, sizeof(Entry*) * _num_buckets); + + /* bucket sizes table */ + _bucket_sizes = NEW_C_HEAP_ARRAY(juint, _num_buckets, mtSymbol); + memset(_bucket_sizes, 0, sizeof(juint) * _num_buckets); + + stats->hashentry_count = _num_entries; + // Compact buckets' entries will have only the 4-byte offset, but + // we don't know how many there will be at this point. So use a + // conservative estimate here. The size is adjusted later when we + // write out the buckets. + stats->hashentry_bytes = _num_entries * 8; + stats->bucket_count = _num_buckets; + stats->bucket_bytes = _num_buckets * (sizeof(juint) + sizeof(jushort)); + + // See compactHashtable.hpp for table layout + _required_bytes = sizeof(juint) * 2; // _base_address, written as 2 juints + _required_bytes+= sizeof(juint) + // num_entries + sizeof(juint) + // num_buckets + sizeof(juint) + // table_end_offset + stats->hashentry_bytes + + stats->bucket_bytes; +} + +CompactHashtableWriter::~CompactHashtableWriter() { + for (int index = 0; index < _num_buckets; index++) { + Entry* next = NULL; + for (Entry* tent = _buckets[index]; tent; tent = next) { + next = tent->next(); + delete tent; + } + } + + FREE_C_HEAP_ARRAY(juint, _bucket_sizes); + FREE_C_HEAP_ARRAY(Entry*, _buckets); +} + +// Calculate the number of buckets in the temporary hash table +int CompactHashtableWriter::number_of_buckets(int num_entries) { + const int buksize = (int)SharedSymbolTableBucketSize; + int num_buckets = (num_entries + buksize - 1) / buksize; + num_buckets = (num_buckets + 1) & (~0x01); + + return num_buckets; +} + +// Add a symbol entry to the temporary hash table +void CompactHashtableWriter::add(unsigned int hash, Entry* entry) { + int index = hash % _num_buckets; + entry->set_next(_buckets[index]); + _buckets[index] = entry; + _bucket_sizes[index] ++; +} + +// Write the compact table's bucket infos +juint* CompactHashtableWriter::dump_table(juint* p, NumberSeq* summary) { + int index; + juint* compact_table = p; + // The start of the buckets, skip the compact_bucket_infos table + juint offset = _num_buckets; + + for (index = 0; index < _num_buckets; index++) { + int bucket_size = _bucket_sizes[index]; + if (bucket_size == 1) { + // bucket with one entry is compacted and only has the symbol offset + compact_table[index] = BUCKET_INFO(offset, COMPACT_BUCKET_TYPE); + offset += bucket_size; // each entry contains symbol offset only + } else { + // regular bucket, each entry is a symbol (hash, offset) pair + compact_table[index] = BUCKET_INFO(offset, REGULAR_BUCKET_TYPE); + offset += bucket_size * 2; // each hash entry is 2 juints + } + if (offset & ~BUCKET_OFFSET_MASK) { + vm_exit_during_initialization("CompactHashtableWriter::dump_table: Overflow! " + "Too many symbols."); + } + summary->add(bucket_size); + } + + return compact_table; +} + +// Write the compact table's entries +juint* CompactHashtableWriter::dump_buckets(juint* compact_table, juint* p, + NumberSeq* summary) { + uintx base_address = uintx(MetaspaceShared::shared_rs()->base()); + uintx max_delta = uintx(MetaspaceShared::shared_rs()->size()); + assert(max_delta <= 0x7fffffff, "range check"); + int num_compact_buckets = 0; + + for (int index = 0; index < _num_buckets; index++) { + juint count = 0; + int bucket_size = _bucket_sizes[index]; + int bucket_type = BUCKET_TYPE(compact_table[index]); + + if (bucket_size == 1) { + assert(bucket_type == COMPACT_BUCKET_TYPE, "Bad bucket type"); + num_compact_buckets ++; + } + for (Entry* tent = _buckets[index]; tent; + tent = tent->next()) { + if (bucket_type == REGULAR_BUCKET_TYPE) { + *p++ = juint(tent->hash()); // write symbol hash + } + uintx deltax = uintx(tent->value()) - base_address; + assert(deltax < max_delta, "range check"); + juint delta = juint(deltax); + *p++ = delta; // write symbol offset + count ++; + } + assert(count == _bucket_sizes[index], "sanity"); + } + + // Adjust the hashentry_bytes in CompactHashtableStats. Each compact + // bucket saves 4-byte. + CompactHashtableStats* stats = &MetaspaceShared::stats()->symbol; + stats->hashentry_bytes -= num_compact_buckets * 4; + + return p; +} + +// Write the compact table +void CompactHashtableWriter::dump(char** top, char* end) { + NumberSeq summary; + char* old_top = *top; + juint* patch_addr; + juint* p = (juint*)(*top); + + uintx base_address = uintx(MetaspaceShared::shared_rs()->base()); + +#ifdef _LP64 + *p++ = juint(base_address >> 32); +#else + *p++ = 0; +#endif + *p++ = juint(base_address & 0xffffffff); // base address + *p++ = _num_entries; // number of entries in the table + *p++ = _num_buckets; // number of buckets in the table + patch_addr = p; + *p++ = 0; // table_end offset, patched after dumping the table + + juint* compact_table = dump_table(p, &summary); + juint* first_bucket = compact_table + _num_buckets; + juint* bucket_end = dump_buckets(compact_table, first_bucket, &summary); + // patch the the table end offset + *patch_addr = bucket_end - compact_table; + + assert(bucket_end <= (juint*)end, "cannot write past end"); + *top = (char*)bucket_end; + + if (PrintSharedSpaces) { + double avg_cost = 0.0; + if (_num_entries > 0) { + avg_cost = double(_required_bytes)/double(_num_entries); + } + tty->print_cr("Shared %s table stats -------- base: " PTR_FORMAT, _table_name, (intptr_t)base_address); + tty->print_cr("Number of entries : %9d", _num_entries); + tty->print_cr("Total bytes used : %9d", (int)((*top) - old_top)); + tty->print_cr("Average bytes per entry : %9.3f", avg_cost); + tty->print_cr("Average bucket size : %9.3f", summary.avg()); + tty->print_cr("Variance of bucket size : %9.3f", summary.variance()); + tty->print_cr("Std. dev. of bucket size: %9.3f", summary.sd()); + tty->print_cr("Maximum bucket size : %9d", (int)summary.maximum()); + } +} + +///////////////////////////////////////////////////////////// +// +// The CompactHashtable implementation +// +template const char* CompactHashtable::init(const char* buffer) { + assert(!DumpSharedSpaces, "run-time only"); + juint*p = (juint*)buffer; + juint upper = *p++; + juint lower = *p++; +#ifdef _LP64 + _base_address = (uintx(upper) << 32 ) + uintx(lower); +#else + _base_address = uintx(lower); +#endif + _entry_count = *p++; + _bucket_count = *p++; + _table_end_offset = *p++; + _buckets = p; + + juint *end = _buckets + _table_end_offset; + return (const char*)end; +} + +// Explicitly instantiate these types +template class CompactHashtable; + +#ifndef O_BINARY // if defined (Win32) use binary files. +#define O_BINARY 0 // otherwise do nothing. +#endif + +//////////////////////////////////////////////////////// +// +// HashtableTextDump +// +HashtableTextDump::HashtableTextDump(const char* filename) : _fd(-1) { + struct stat st; + if (os::stat(filename, &st) != 0) { + quit("Unable to get hashtable dump file size", filename); + } + _size = st.st_size; + _fd = open(filename, O_RDONLY | O_BINARY, 0); + if (_fd < 0) { + quit("Unable to open hashtable dump file", filename); + } + _base = os::map_memory(_fd, filename, 0, NULL, _size, true, false); + if (_base == NULL) { + quit("Unable to map hashtable dump file", filename); + } + _p = _base; + _end = _base + st.st_size; + _filename = filename; +} + +HashtableTextDump::~HashtableTextDump() { + os::unmap_memory((char*)_base, _size); + if (_fd >= 0) { + close(_fd); + } +} + +void HashtableTextDump::quit(const char* err, const char* msg) { + vm_exit_during_initialization(err, msg); +} + +void HashtableTextDump::corrupted(const char *p) { + char info[60]; + sprintf(info, "corrupted at pos %d", (int)(p - _base)); + quit(info, _filename); +} + +bool HashtableTextDump::skip_newline() { + if (_p[0] == '\r' && _p[1] == '\n') { + _p += 2; + } else if (_p[0] == '\n') { + _p += 1; + } else { + corrupted(_p); + } + return true; +} + +int HashtableTextDump::skip(char must_be_char) { + corrupted_if(remain() < 1); + corrupted_if(*_p++ != must_be_char); + return 0; +} + +void HashtableTextDump::skip_past(char c) { + for (;;) { + corrupted_if(remain() < 1); + if (*_p++ == c) { + return; + } + } +} + +void HashtableTextDump::check_version(const char* ver) { + int len = (int)strlen(ver); + corrupted_if(remain() < len); + if (strncmp(_p, ver, len) != 0) { + quit("wrong version of hashtable dump file", _filename); + } + _p += len; + skip_newline(); +} + + +int HashtableTextDump::scan_prefix() { + // Expect /[0-9]+: / + int utf8_length = get_num(':'); + if (*_p != ' ') { + corrupted(_p); + } + _p++; + return utf8_length; +} + +int HashtableTextDump::scan_prefix2() { + // Expect /[0-9]+ (-|)[0-9]+: / + int utf8_length = get_num(' '); + if (*_p == '-') { + _p++; + } + (void)get_num(':'); + if (*_p != ' ') { + corrupted(_p); + } + _p++; + return utf8_length; +} + +jchar HashtableTextDump::unescape(const char* from, const char* end, int count) { + jchar value = 0; + + corrupted_if(from + count > end); + + for (int i=0; i 0 && from < end; n--) { + if (*from != '\\') { + *to++ = *from++; + } else { + corrupted_if(from + 2 > end); + char c = from[1]; + from += 2; + switch (c) { + case 'x': + { + jchar value = unescape(from, end, 2); + from += 2; + assert(value <= 0xff, "sanity"); + *to++ = (char)(value & 0xff); + } + break; + case 't': *to++ = '\t'; break; + case 'n': *to++ = '\n'; break; + case 'r': *to++ = '\r'; break; + case '\\': *to++ = '\\'; break; + default: + ShouldNotReachHere(); + } + } + } + corrupted_if(n > 0); // expected more chars but file has ended + _p = from; + skip_newline(); +} + +// NOTE: the content is NOT the same as +// UTF8::as_quoted_ascii(const char* utf8_str, int utf8_length, char* buf, int buflen). +// We want to escape \r\n\t so that output [1] is more readable; [2] can be more easily +// parsed by scripts; [3] quickly processed by HashtableTextDump::get_utf8() +void HashtableTextDump::put_utf8(outputStream* st, const char* utf8_string, int utf8_length) { + const char *c = utf8_string; + const char *end = c + utf8_length; + for (; c < end; c++) { + switch (*c) { + case '\t': st->print("\\t"); break; + case '\r': st->print("\\r"); break; + case '\n': st->print("\\n"); break; + case '\\': st->print("\\\\"); break; + default: + if (isprint(*c)) { + st->print("%c", *c); + } else { + st->print("\\x%02x", ((unsigned int)*c) & 0xff); + } + } + } +} --- /dev/null 2014-09-29 03:28:39.162223999 -0400 +++ new/src/share/vm/classfile/compactHashtable.hpp 2014-12-03 19:33:19.519311181 -0500 @@ -0,0 +1,405 @@ +/* + * Copyright (c) 1997, 2014, 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. + * + */ + +#ifndef SHARE_VM_CLASSFILE_COMPACTHASHTABLE_HPP +#define SHARE_VM_CLASSFILE_COMPACTHASHTABLE_HPP + +#include "classfile/stringTable.hpp" +#include "classfile/symbolTable.hpp" +#include "memory/allocation.inline.hpp" +#include "oops/symbol.hpp" +#include "services/diagnosticCommand.hpp" +#include "utilities/hashtable.hpp" + +class NumberSeq; + +// Stats for symbol tables in the CDS archive +class CompactHashtableStats VALUE_OBJ_CLASS_SPEC { +public: + int hashentry_count; + int hashentry_bytes; + int bucket_count; + int bucket_bytes; +}; + +///////////////////////////////////////////////////////////////////////// +// +// The compact hash table writer. Used at dump time for writing out +// the compact table to the shared archive. +// +// At dump time, the CompactHashtableWriter obtains all entries from the +// symbol table and adds them to a new temporary hash table. The hash +// table size (number of buckets) is calculated using +// '(num_entries + bucket_size - 1) / bucket_size'. The default bucket +// size is 4 and can be changed by -XX:SharedSymbolTableBucketSize option. +// 4 is chosen because it produces smaller sized bucket on average for +// faster lookup. It also has relatively small number of empty buckets and +// good distribution of the entries. +// +// We use a simple hash function (symbol_hash % num_bucket) for the table. +// The new table is compacted when written out. Please see comments +// above the CompactHashtable class for the table layout detail. The bucket +// offsets are written to the archive as part of the compact table. The +// bucket offset is encoded in the low 30-bit (0-29) and the bucket type +// (regular or compact) are encoded in bit[31, 30]. For buckets with more +// than one entry, both symbol hash and symbol offset are written to the +// table. For buckets with only one entry, only the symbol offset is written +// to the table and the buckets are tagged as compact in their type bits. +// Buckets without entry are skipped from the table. Their offsets are +// still written out for faster lookup. +// +class CompactHashtableWriter: public StackObj { +public: + class Entry: public CHeapObj { + Entry* _next; + unsigned int _hash; + void* _literal; + + public: + Entry(unsigned int hash, Symbol *symbol) : _next(NULL), _hash(hash), _literal(symbol) {} + + void *value() { + return _literal; + } + Symbol *symbol() { + return (Symbol*)_literal; + } + unsigned int hash() { + return _hash; + } + Entry *next() {return _next;} + void set_next(Entry *p) {_next = p;} + }; // class CompactHashtableWriter::Entry + +private: + static int number_of_buckets(int num_entries); + + const char* _table_name; + int _num_entries; + int _num_buckets; + juint* _bucket_sizes; + Entry** _buckets; + int _required_bytes; + +public: + // This is called at dump-time only + CompactHashtableWriter(const char* table_name, int num_entries, CompactHashtableStats* stats); + ~CompactHashtableWriter(); + + int get_required_bytes() { + return _required_bytes; + } + + void add(unsigned int hash, Symbol* symbol) { + add(hash, new Entry(hash, symbol)); + } + +private: + void add(unsigned int hash, Entry* entry); + juint* dump_table(juint* p, NumberSeq* summary); + juint* dump_buckets(juint* table, juint* p, NumberSeq* summary); + +public: + void dump(char** top, char* end); +}; + +#define REGULAR_BUCKET_TYPE 0 +#define COMPACT_BUCKET_TYPE 1 +#define BUCKET_OFFSET_MASK 0x3FFFFFFF +#define BUCKET_OFFSET(info) ((info) & BUCKET_OFFSET_MASK) +#define BUCKET_TYPE_SHIFT 30 +#define BUCKET_TYPE(info) (((info) & ~BUCKET_OFFSET_MASK) >> BUCKET_TYPE_SHIFT) +#define BUCKET_INFO(offset, type) (((type) << BUCKET_TYPE_SHIFT) | ((offset) & BUCKET_OFFSET_MASK)) + +///////////////////////////////////////////////////////////////////////////// +// +// CompactHashtable is used to stored the CDS archive's symbol table. Used +// at runtime only to access the compact table from the archive. +// +// Because these tables are read-only (no entries can be added/deleted) at run-time +// and tend to have large number of entries, we try to minimize the footprint +// cost per entry. +// +// Layout of compact symbol table in the shared archive: +// +// uintx base_address; +// juint num_symbols; +// juint num_buckets; +// juint table_end_offset; +// juint bucket_infos[num_buckets]; /* bit[31,30]: type; bit[29-0]: offset */ +// juint table[] +// +// ----------------------------------- +// | base_address | num_symbols | +// |---------------------------------| +// | num_buckets | table_end_offset| +// |---------------------------------| +// | bucket_info0 | bucket_info1 | +// | bucket_info2 ... | +// | .... | +// |---------------------------------| +// | entry0 | +// | entry1 | +// | entry2 | +// | | +// | ... | +// ----------------------------------- +// +// There are two types of buckets, regular buckets and compact buckets. The +// compact buckets have '01' in their highest 2-bit, and regular buckets have +// '00' in their highest 2-bit. +// +// For normal buckets, each symbol's entry is 8 bytes in the table[]: +// juint hash; /* symbol hash */ +// juint offset; /* Symbol* sym = (Symbol*)(base_address + offset) */ +// +// For compact buckets, each entry has only the 4-byte 'offset' in the table[]. +// +// See CompactHashtable::lookup() for how the table is searched at runtime. +// See CompactHashtableWriter::dump() for how the table is written at CDS +// dump time. +// +template class CompactHashtable VALUE_OBJ_CLASS_SPEC { + uintx _base_address; + juint _entry_count; + juint _bucket_count; + juint _table_end_offset; + juint* _buckets; + + inline bool equals(T entry, const char* name, int len) { + if (entry->equals(name, len)) { + assert(entry->refcount() == -1, "must be shared"); + return true; + } else { + return false; + } + } + +public: + CompactHashtable() { + _entry_count = 0; + _bucket_count = 0; + _table_end_offset = 0; + _buckets = 0; + } + const char* init(const char *buffer); + + // Lookup an entry from the compact table + inline T lookup(const N* name, unsigned int hash, int len) { + if (_entry_count > 0) { + assert(!DumpSharedSpaces, "run-time only"); + int index = hash % _bucket_count; + juint bucket_info = _buckets[index]; + juint bucket_offset = BUCKET_OFFSET(bucket_info); + int bucket_type = BUCKET_TYPE(bucket_info); + juint* bucket = _buckets + bucket_offset; + juint* bucket_end = _buckets; + + if (bucket_type == COMPACT_BUCKET_TYPE) { + // the compact bucket has one entry with symbol offset only + T entry = (T)((void*)(_base_address + bucket[0])); + if (equals(entry, name, len)) { + return entry; + } + } else { + // This is a regular bucket, which has more than one + // entries. Each entry is a pair of symbol (hash, offset). + + if (index == int(_bucket_count - 1)) { + //last bucket, seek to the table end + bucket_end += _table_end_offset; + } else { + //seek to the next bucket start + bucket_end += BUCKET_OFFSET(_buckets[index + 1]); + } + + while (bucket < bucket_end) { + unsigned int h = (unsigned int)(bucket[0]); + if (h == hash) { + juint offset = bucket[1]; + T entry = (T)((void*)(_base_address + offset)); + if (equals(entry, name, len)) { + return entry; + } + } + bucket += 2; + } + } + } + return NULL; + } +}; + +//////////////////////////////////////////////////////////////////////// +// +// Read/Write the contents of a hashtable textual dump (created by +// SymbolTable::dump). +// Because the dump file may be big (hundred of MB in extreme cases), +// we use mmap for fast access when reading it. +// +class HashtableTextDump VALUE_OBJ_CLASS_SPEC { + int _fd; + const char* _base; + const char* _p; + const char* _end; + const char* _filename; + size_t _size; +public: + HashtableTextDump(const char* filename); + ~HashtableTextDump(); + + void quit(const char* err, const char* msg); + + inline int remain() { + return (int)(_end - _p); + } + + void corrupted(const char *p); + + inline void corrupted_if(bool cond) { + if (cond) { + corrupted(_p); + } + } + + bool skip_newline(); + int skip(char must_be_char); + void skip_past(char c); + void check_version(const char* ver); + + inline int get_num(char delim) { + const char* p = _p; + const char* end = _end; + int num = 0; + + while (p < end) { + char c = *p ++; + if ('0' <= c && c <= '9') { + num = num * 10 + (c - '0'); + } else if (c == delim) { + _p = p; + return num; + } else { + corrupted(p-1); + } + } + corrupted(_end); + ShouldNotReachHere(); + return 0; + } + + int scan_prefix(); + int scan_prefix2(); + + jchar unescape(const char* from, const char* end, int count); + void get_utf8(char* utf8_buffer, int utf8_length); + static void put_utf8(outputStream* st, const char* utf8_string, int utf8_length); +}; + +/////////////////////////////////////////////////////////////////////// +// +// jcmd command support for symbol table and string table dumping: +// VM.symboltable -verbose: for dumping the symbol table +// VM.stringtable -verbose: for dumping the string table +// +class VM_DumpHashtable : public VM_Operation { +private: + outputStream* _out; + int _which; + bool _verbose; +public: + enum { + DumpSymbols = 1 << 0, + DumpStrings = 1 << 1, + DumpSysDict = 1 << 2 // not implemented yet + }; + VM_DumpHashtable(outputStream* out, int which, bool verbose) { + _out = out; + _which = which; + _verbose = verbose; + } + + virtual VMOp_Type type() const { return VMOp_DumpHashtable; } + + virtual void doit() { + switch (_which) { + case DumpSymbols: + SymbolTable::dump(_out, _verbose); + break; + case DumpStrings: + StringTable::dump(_out, _verbose); + break; + default: + ShouldNotReachHere(); + } + } +}; + +class SymboltableDCmd : public DCmdWithParser { +protected: + DCmdArgument _verbose; +public: + SymboltableDCmd(outputStream* output, bool heap); + static const char* name() { + return "VM.symboltable"; + } + static const char* description() { + return "Dump symbol table."; + } + static const char* impact() { + return "Medium: Depends on Java content."; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", + "monitor", NULL}; + return p; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + +class StringtableDCmd : public DCmdWithParser { +protected: + DCmdArgument _verbose; +public: + StringtableDCmd(outputStream* output, bool heap); + static const char* name() { + return "VM.stringtable"; + } + static const char* description() { + return "Dump string table."; + } + static const char* impact() { + return "Medium: Depends on Java content."; + } + static const JavaPermission permission() { + JavaPermission p = {"java.lang.management.ManagementPermission", + "monitor", NULL}; + return p; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + +#endif // SHARE_VM_CLASSFILE_COMPACTHASHTABLE_HPP --- /dev/null 2014-09-29 03:28:39.162223999 -0400 +++ new/test/runtime/SharedArchiveFile/DumpSymbolAndStringTable.java 2014-12-03 19:33:25.423645060 -0500 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014, 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 8059510 + * @summary Test jcmd VM.symboltable and VM.stringtable options + * @library /testlibrary + * @run main/othervm -XX:+UnlockDiagnosticVMOptions DumpSymbolAndStringTable + */ + +import com.oracle.java.testlibrary.*; + +public class DumpSymbolAndStringTable { + public static void main(String[] args) throws Exception { + // Grab my own PID + String pid = Integer.toString(ProcessTools.getProcessId()); + + ProcessBuilder pb = new ProcessBuilder(); + pb.command(new String[] {JDKToolFinder.getJDKTool("jcmd"), pid, "VM.symboltable", "-verbose"}); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + try { + output.shouldContain("24 2: DumpSymbolAndStringTable\n"); + } catch (RuntimeException e) { + output.shouldContain("Unknown diagnostic command"); + } + + pb.command(new String[] {JDKToolFinder.getJDKTool("jcmd"), pid, "VM.stringtable", "-verbose"}); + output = new OutputAnalyzer(pb.start()); + try { + output.shouldContain("16: java.lang.String\n"); + } catch (RuntimeException e) { + output.shouldContain("Unknown diagnostic command"); + } + } +} --- /dev/null 2014-09-29 03:28:39.162223999 -0400 +++ new/test/runtime/SharedArchiveFile/SharedSymbolTableBucketSize.java 2014-12-03 19:33:28.363811325 -0500 @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014, 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 8059510 + * @summary Test SharedSymbolTableBucketSize option + * @library /testlibrary + */ + +import com.oracle.java.testlibrary.*; + +public class SharedSymbolTableBucketSize { + public static void main(String[] args) throws Exception { + int bucket_size = 8; + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-Xshare:dump", "-XX:+PrintSharedSpaces", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:SharedArchiveFile=./sample.jsa", + "-XX:SharedSymbolTableBucketSize=" + Integer.valueOf(bucket_size)); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("Loading classes to share"); + output.shouldHaveExitValue(0); + + String s = output.firstMatch("Average bucket size : .*"); + Float f = Float.parseFloat(s.substring(25)); + int size = Math.round(f); + if (size != bucket_size) { + throw new Exception("FAILED: incorrect bucket size " + size + + ", expect " + bucket_size); + } + + + // Invalid SharedSymbolTableBucketSize input + String input[] = {"-XX:SharedSymbolTableBucketSize=-1", + "-XX:SharedSymbolTableBucketSize=2.5"}; + for (int i = 0; i < input.length; i++) { + pb = ProcessTools.createJavaProcessBuilder( + "-Xshare:dump", "-XX:+PrintSharedSpaces", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:SharedArchiveFile=./sample.jsa", + input[i]); + output = new OutputAnalyzer(pb.start()); + output.shouldContain("Improperly specified VM option"); + } + } +}