8059510: Compact symbol table layout inside shared archive

Use separate compact table for shared symbols.

Reviewed-by: iklam, gziemski, shade, sla, jrose
This commit is contained in:
Jiangli Zhou 2014-12-17 23:34:52 -05:00
parent 51bda75a8f
commit b0ad035af4
14 changed files with 1172 additions and 70 deletions

View file

@ -0,0 +1,417 @@
/*
* 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 <sys/stat.h>
/////////////////////////////////////////////////////
//
// 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 + 1) * (sizeof(juint));
_stats = stats;
// 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
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, juint** first_bucket,
NumberSeq* summary) {
int index;
juint* compact_table = p;
// Find the start of the buckets, skip the compact_bucket_infos table
// and the table end offset.
juint offset = _num_buckets + 1;
*first_bucket = compact_table + offset;
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);
}
// Mark the end of the table
compact_table[_num_buckets] = BUCKET_INFO(offset, TABLEEND_BUCKET_TYPE);
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;
assert(p != NULL, "sanity");
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.
_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* p = (juint*)(*top);
uintx base_address = uintx(MetaspaceShared::shared_rs()->base());
*p++ = high(base_address);
*p++ = low (base_address); // base address
*p++ = _num_entries; // number of entries in the table
*p++ = _num_buckets; // number of buckets in the table
juint* first_bucket = NULL;
juint* compact_table = dump_table(p, &first_bucket, &summary);
juint* bucket_end = dump_buckets(compact_table, first_bucket, &summary);
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 <class T, class N> const char* CompactHashtable<T, N>::init(const char* buffer) {
assert(!DumpSharedSpaces, "run-time only");
juint*p = (juint*)buffer;
juint upper = *p++;
juint lower = *p++;
_base_address = uintx(jlong_from(upper, lower));
_entry_count = *p++;
_bucket_count = *p++;
_buckets = p;
_table_end_offset = BUCKET_OFFSET(p[_bucket_count]); // located at the end of the bucket_info table
juint *end = _buckets + _table_end_offset;
return (const char*)end;
}
// Explicitly instantiate these types
template class CompactHashtable<Symbol*, char>;
#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<count; i++) {
char c = *from++;
switch (c) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
value = (value << 4) + c - '0';
break;
case 'a': case 'b': case 'c':
case 'd': case 'e': case 'f':
value = (value << 4) + 10 + c - 'a';
break;
case 'A': case 'B': case 'C':
case 'D': case 'E': case 'F':
value = (value << 4) + 10 + c - 'A';
break;
default:
ShouldNotReachHere();
}
}
return value;
}
void HashtableTextDump::get_utf8(char* utf8_buffer, int utf8_length) {
// cache in local vars
const char* from = _p;
const char* end = _end;
char* to = utf8_buffer;
int n = utf8_length;
for (; n > 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);
}
}
}
}

View file

@ -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<mtSymbol> {
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;
CompactHashtableStats* _stats;
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, juint** first_bucket, 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 TABLEEND_BUCKET_TYPE 3
#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 bucket_infos[num_buckets+1]; // bit[31,30]: type; bit[29-0]: offset
// juint table[]
//
// -----------------------------------
// | base_address | num_symbols |
// |---------------------------------|
// | num_buckets | bucket_info0 |
// |---------------------------------|
// | bucket_info1 | bucket_info2 |
// | bucket_info3 ... |
// | .... | table_end_info |
// |---------------------------------|
// | entry0 |
// | entry1 |
// | entry2 |
// | |
// | ... |
// -----------------------------------
//
// The size of the bucket_info table is 'num_buckets + 1'. Each entry of the
// bucket_info table is a 32-bit encoding of the bucket type and bucket offset,
// with the type in the left-most 2-bit and offset in the remaining 30-bit.
// The last entry is a special type. It contains the offset of the last
// bucket end. We use that information when traversing the compact table.
//
// 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 T, class N> 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).
// Seek until the end of the bucket.
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<bool> _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<bool> _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

View file

@ -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"
@ -379,8 +380,36 @@ void StringTable::verify() {
}
}
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<oop, mtSymbol>* 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(
@ -558,3 +587,28 @@ void StringTable::rehash_table() {
_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;
}
}

View file

@ -118,7 +118,7 @@ public:
// Debugging
static void verify();
static void dump(outputStream* st);
static void dump(outputStream* st, bool verbose=false);
enum VerifyMesgModes {
_verify_quietly = 0,

View file

@ -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 @@ SymbolTable* SymbolTable::_the_table = NULL;
// Static arena for symbols that are not deallocated
Arena* SymbolTable::_arena = NULL;
bool SymbolTable::_needs_rehashing = false;
bool SymbolTable::_lookup_shared_first = false;
CompactHashtable<Symbol*, char> 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 @@ void SymbolTable::rehash_table() {
// 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<Symbol*, mtSymbol>* 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 @@ Symbol* SymbolTable::lookup(int index, const char* name,
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::verify() {
}
}
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<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();
}
}
}
}
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<Symbol*, mtSymbol>* 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 @@ void SymbolTable::print() {
}
}
#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;
}
}

View file

@ -73,6 +73,8 @@ class TempNewSymbol : public StackObj {
operator Symbol*() { return _temp; }
};
template <class T, class N> class CompactHashtable;
class SymbolTable : public RehashableHashtable<Symbol*, mtSymbol> {
friend class VMStructs;
friend class ClassFileParser;
@ -83,11 +85,15 @@ private:
// 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<Symbol*, char> _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 @@ private:
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 @@ public:
initialize_symbols(symbol_alloc_arena_size);
}
static void create_table(HashtableBucket<mtSymbol>* 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 @@ public:
// 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<Symbol*, mtSymbol>::copy_buckets(top, end);
}
static void copy_table(char** top, char*end) {
the_table()->Hashtable<Symbol*, mtSymbol>::copy_table(top, end);
}
static void reverse(void* boundary = NULL) {
the_table()->Hashtable<Symbol*, mtSymbol>::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();

View file

@ -48,6 +48,8 @@ int MetaspaceShared::_max_alignment = 0;
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 @@ public:
#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 @@ void DumpAllocClosure::dump_stats(int ro_all, int rw_all, int md_all, int mc_all
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<Symbol*, mtSymbol>);
int symbuck_count = SymbolTable::the_table()->table_size();
int symbuck_bytes = symbuck_count * sizeof(HashtableBucket<mtSymbol>);
MetaspaceSharedStats *stats = MetaspaceShared::stats();
_counts[RW][SymbolHashentryType] = symbol_count;
_bytes [RW][SymbolHashentryType] = symhash_bytes;
other_bytes -= symhash_bytes;
// symbols
_counts[RW][SymbolHashentryType] = stats->symbol.hashentry_count;
_bytes [RW][SymbolHashentryType] = stats->symbol.hashentry_bytes;
other_bytes -= stats->symbol.hashentry_bytes;
_counts[RW][SymbolBucketsType] = symbuck_count;
_bytes [RW][SymbolBucketsType] = symbuck_bytes;
other_bytes -= symbuck_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,13 @@ public:
VMOp_Type type() const { return VMOp_PopulateDumpSharedSpace; }
void doit(); // outline because gdb sucks
private:
void handle_misc_data_space_failure(bool success) {
if (!success) {
report_out_of_shared_space(SharedMiscData);
}
}
}; // class VM_PopulateDumpSharedSpace
@ -517,9 +524,8 @@ void VM_PopulateDumpSharedSpace::doit() {
// 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 +534,6 @@ void VM_PopulateDumpSharedSpace::doit() {
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 +1005,12 @@ void MetaspaceShared::initialize_shared_spaces() {
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<mtSymbol>*)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 +1019,7 @@ void MetaspaceShared::initialize_shared_spaces() {
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<mtClass>*)buffer,
sharedDictionaryLen,
@ -1041,18 +1041,10 @@ void MetaspaceShared::initialize_shared_spaces() {
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.
// list elements (HashtableEntry objects) for the shared dictionary
// and package info table.
int len = *(intptr_t*)buffer; // skip over symbol table entries
buffer += sizeof(intptr_t);
buffer += len;
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;

View file

@ -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;
@ -123,6 +133,10 @@ class MetaspaceShared : AllStatic {
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

View file

@ -1176,9 +1176,7 @@ bool universe_post_init() {
MemoryService::set_universe_heap(Universe::_collectedHeap);
#if INCLUDE_CDS
if (UseSharedSpaces) {
SharedClassUtil::initialize(CHECK_false);
}
SharedClassUtil::initialize(CHECK_false);
#endif
return true;
}

View file

@ -3782,6 +3782,9 @@ class CommandLineFlags {
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.") \

View file

@ -100,6 +100,7 @@
template(RotateGCLog) \
template(WhiteBoxOperation) \
template(ClassLoaderStatsOperation) \
template(DumpHashtable) \
template(MarkActiveNMethods) \
template(PrintCompileQueue) \
template(PrintCodeList) \

View file

@ -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 @@ void DCmdRegistrant::register_dcmds(){
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<HeapDumpDCmd>(DCmd_Source_Internal | DCmd_Source_AttachAPI, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassHistogramDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassStatsDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<SymboltableDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<StringtableDCmd>(full_export, true, false));
#endif // INCLUDE_SERVICES
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<RotateGCLogDCmd>(full_export, true, false));

View file

@ -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");
}
}
}

View file

@ -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");
}
}
}