mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-15 08:34:30 +02:00
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:
parent
51bda75a8f
commit
b0ad035af4
14 changed files with 1172 additions and 70 deletions
417
hotspot/src/share/vm/classfile/compactHashtable.cpp
Normal file
417
hotspot/src/share/vm/classfile/compactHashtable.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
405
hotspot/src/share/vm/classfile/compactHashtable.hpp
Normal file
405
hotspot/src/share/vm/classfile/compactHashtable.hpp
Normal 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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.") \
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
template(RotateGCLog) \
|
||||
template(WhiteBoxOperation) \
|
||||
template(ClassLoaderStatsOperation) \
|
||||
template(DumpHashtable) \
|
||||
template(MarkActiveNMethods) \
|
||||
template(PrintCompileQueue) \
|
||||
template(PrintCodeList) \
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue