8213587: Speed up CDS dump time by using resizable hashtables

Reviewed-by: jiangli, coleenp, gziemski
This commit is contained in:
Ioi Lam 2018-11-20 20:00:15 -08:00
parent 6d3df94e5e
commit 6d269930fd
21 changed files with 264 additions and 105 deletions

View File

@ -389,8 +389,8 @@ Klass* ClassListParser::load_current_class(TRAPS) {
InstanceKlass* ik = InstanceKlass::cast(klass);
int id = this->id();
SystemDictionaryShared::update_shared_entry(ik, id);
InstanceKlass* old = table()->lookup(id);
if (old != NULL && old != ik) {
InstanceKlass** old_ptr = table()->lookup(id);
if (old_ptr != NULL) {
error("Duplicated ID %d for class %s", id, _class_name);
}
table()->add(id, ik);
@ -404,11 +404,12 @@ bool ClassListParser::is_loading_from_source() {
}
InstanceKlass* ClassListParser::lookup_class_by_id(int id) {
InstanceKlass* klass = table()->lookup(id);
if (klass == NULL) {
InstanceKlass** klass_ptr = table()->lookup(id);
if (klass_ptr == NULL) {
error("Class ID %d has not been defined", id);
}
return klass;
assert(*klass_ptr != NULL, "must be");
return *klass_ptr;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -27,30 +27,12 @@
#include "utilities/exceptions.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/hashtable.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/hashtable.inline.hpp"
class CDSClassInfo;
// Look up from ID -> InstanceKlass*
class ID2KlassTable : public Hashtable<InstanceKlass*, mtClass> {
class ID2KlassTable : public KVHashtable<int, InstanceKlass*, mtInternal> {
public:
ID2KlassTable() : Hashtable<InstanceKlass*, mtClass>(1987, sizeof(HashtableEntry<InstanceKlass*, mtClass>)) { }
void add(int id, InstanceKlass* klass) {
unsigned int hash = (unsigned int)id;
HashtableEntry<InstanceKlass*, mtClass>* entry = new_entry(hash, klass);
add_entry(hash_to_index(hash), entry);
}
InstanceKlass* lookup(int id) {
unsigned int hash = (unsigned int)id;
int index = hash_to_index(id);
for (HashtableEntry<InstanceKlass*, mtClass>* e = bucket(index); e != NULL; e = e->next()) {
if (e->hash() == hash) {
return e->literal();
}
}
return NULL;
}
ID2KlassTable() : KVHashtable<int, InstanceKlass*, mtInternal>(1987) {}
};
class ClassListParser : public StackObj {

View File

@ -25,7 +25,6 @@
#include "precompiled.hpp"
#include "classfile/classFileParser.hpp"
#include "classfile/classFileStream.hpp"
#include "classfile/classListParser.hpp"
#include "classfile/classLoader.inline.hpp"
#include "classfile/classLoaderExt.hpp"
#include "classfile/classLoaderData.inline.hpp"
@ -257,7 +256,6 @@ void ClassLoaderExt::finalize_shared_paths_misc_info() {
// the "source:" in the class list file (see classListParser.cpp), and can be a directory or
// a JAR file.
InstanceKlass* ClassLoaderExt::load_class(Symbol* name, const char* path, TRAPS) {
assert(name != NULL, "invariant");
assert(DumpSharedSpaces, "this function is only used with -Xshare:dump");
ResourceMark rm(THREAD);

View File

@ -27,7 +27,7 @@
#include "oops/array.hpp"
#include "oops/symbol.hpp"
#include "utilities/hashtable.hpp"
#include "utilities/growableArray.hpp"
template <

View File

@ -67,7 +67,6 @@ Dictionary::~Dictionary() {
}
assert(number_of_entries() == 0, "should have removed all entries");
assert(new_entry_free_list() == NULL, "entry present on Dictionary's free list");
free_buckets();
}
DictionaryEntry* Dictionary::new_entry(unsigned int hash, InstanceKlass* klass) {

View File

@ -352,7 +352,6 @@ ModuleEntryTable::~ModuleEntryTable() {
}
assert(number_of_entries() == 0, "should have removed all entries");
assert(new_entry_free_list() == NULL, "entry present on ModuleEntryTable's free list");
free_buckets();
}
ModuleEntry* ModuleEntryTable::new_entry(unsigned int hash, Handle module_handle,

View File

@ -191,7 +191,6 @@ PackageEntryTable::~PackageEntryTable() {
}
assert(number_of_entries() == 0, "should have removed all entries");
assert(new_entry_free_list() == NULL, "entry present on PackageEntryTable's free list");
free_buckets();
}
PackageEntry* PackageEntryTable::new_entry(unsigned int hash, Symbol* name, ModuleEntry* module) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -76,7 +76,9 @@ G1CodeRootSetTable::~G1CodeRootSetTable() {
}
}
assert(number_of_entries() == 0, "should have removed all entries");
free_buckets();
// Each of the entries in new_entry_free_list() have been allocated in
// G1CodeRootSetTable::new_entry(). We never call the block allocator
// in BasicHashtable::new_entry().
for (BasicHashtableEntry<mtGC>* e = new_entry_free_list(); e != NULL; e = new_entry_free_list()) {
FREE_C_HEAP_ARRAY(char, e);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -55,13 +55,15 @@ void MetaspaceClosure::push_impl(MetaspaceClosure::Ref* ref, Writability w) {
}
bool UniqueMetaspaceClosure::do_ref(MetaspaceClosure::Ref* ref, bool read_only) {
bool* found = _has_been_visited.get(ref->obj());
bool* found = _has_been_visited.lookup(ref->obj());
if (found != NULL) {
assert(*found == read_only, "must be");
return false; // Already visited: no need to iterate embedded pointers.
} else {
bool isnew = _has_been_visited.put(ref->obj(), read_only);
assert(isnew, "sanity");
_has_been_visited.add(ref->obj(), read_only);
if (_has_been_visited.maybe_grow(MAX_TABLE_SIZE)) {
log_info(cds, hashtables)("Expanded _has_been_visited table to %d", _has_been_visited.table_size());
}
do_unique_ref(ref, read_only);
return true; // Saw this for the first time: iterate the embedded pointers.
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -29,7 +29,7 @@
#include "memory/allocation.hpp"
#include "oops/array.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/resourceHash.hpp"
#include "utilities/hashtable.inline.hpp"
// The metadata hierarchy is separate from the oop hierarchy
class MetaspaceObj; // no C++ vtable
@ -258,25 +258,19 @@ public:
// This is a special MetaspaceClosure that visits each unique MetaspaceObj once.
class UniqueMetaspaceClosure : public MetaspaceClosure {
static const int INITIAL_TABLE_SIZE = 15889;
static const int MAX_TABLE_SIZE = 1000000;
// Do not override. Returns true if we are discovering ref->obj() for the first time.
virtual bool do_ref(Ref* ref, bool read_only);
public:
// Gets called the first time we discover an object.
virtual void do_unique_ref(Ref* ref, bool read_only) = 0;
UniqueMetaspaceClosure() : _has_been_visited(INITIAL_TABLE_SIZE) {}
private:
static unsigned my_hash(const address& a) {
return primitive_hash<address>(a);
}
static bool my_equals(const address& a0, const address& a1) {
return primitive_equals<address>(a0, a1);
}
ResourceHashtable<
address, bool,
UniqueMetaspaceClosure::my_hash, // solaris compiler doesn't like: primitive_hash<address>
UniqueMetaspaceClosure::my_equals, // solaris compiler doesn't like: primitive_equals<address>
15889, // prime number
ResourceObj::C_HEAP> _has_been_visited;
KVHashtable<address, bool, mtInternal> _has_been_visited;
};
#endif // SHARE_VM_MEMORY_METASPACE_ITERATOR_HPP

View File

@ -64,6 +64,7 @@
#include "utilities/align.hpp"
#include "utilities/bitMap.hpp"
#include "utilities/defaultStream.hpp"
#include "utilities/hashtable.inline.hpp"
#if INCLUDE_G1GC
#include "gc/g1/g1CollectedHeap.hpp"
#endif
@ -1067,26 +1068,19 @@ public:
// metaspace data into their final location in the shared regions.
class ArchiveCompactor : AllStatic {
static const int INITIAL_TABLE_SIZE = 8087;
static const int MAX_TABLE_SIZE = 1000000;
static DumpAllocStats* _alloc_stats;
static SortedSymbolClosure* _ssc;
static unsigned my_hash(const address& a) {
return primitive_hash<address>(a);
}
static bool my_equals(const address& a0, const address& a1) {
return primitive_equals<address>(a0, a1);
}
typedef ResourceHashtable<
address, address,
ArchiveCompactor::my_hash, // solaris compiler doesn't like: primitive_hash<address>
ArchiveCompactor::my_equals, // solaris compiler doesn't like: primitive_equals<address>
16384, ResourceObj::C_HEAP> RelocationTable;
typedef KVHashtable<address, address, mtInternal> RelocationTable;
static RelocationTable* _new_loc_table;
public:
static void initialize() {
_alloc_stats = new(ResourceObj::C_HEAP, mtInternal)DumpAllocStats;
_new_loc_table = new(ResourceObj::C_HEAP, mtInternal)RelocationTable;
_new_loc_table = new RelocationTable(INITIAL_TABLE_SIZE);
}
static DumpAllocStats* alloc_stats() {
return _alloc_stats;
@ -1136,15 +1130,17 @@ public:
newtop = _rw_region.top();
}
memcpy(p, obj, bytes);
bool isnew = _new_loc_table->put(obj, (address)p);
assert(_new_loc_table->lookup(obj) == NULL, "each object can be relocated at most once");
_new_loc_table->add(obj, (address)p);
log_trace(cds)("Copy: " PTR_FORMAT " ==> " PTR_FORMAT " %d", p2i(obj), p2i(p), bytes);
assert(isnew, "must be");
if (_new_loc_table->maybe_grow(MAX_TABLE_SIZE)) {
log_info(cds, hashtables)("Expanded _new_loc_table to %d", _new_loc_table->table_size());
}
_alloc_stats->record(ref->msotype(), int(newtop - oldtop), read_only);
}
static address get_new_loc(MetaspaceClosure::Ref* ref) {
address* pp = _new_loc_table->get(ref->obj());
address* pp = _new_loc_table->lookup(ref->obj());
assert(pp != NULL, "must be");
return *pp;
}
@ -1288,7 +1284,7 @@ public:
static Klass* get_relocated_klass(Klass* orig_klass) {
assert(DumpSharedSpaces, "dump time only");
address* pp = _new_loc_table->get((address)orig_klass);
address* pp = _new_loc_table->lookup((address)orig_klass);
assert(pp != NULL, "must be");
Klass* klass = (Klass*)(*pp);
assert(klass->is_klass(), "must be");

View File

@ -1261,4 +1261,17 @@ static inline void* dereference_vptr(const void* addr) {
typedef const char* ccstr;
typedef const char* ccstrlist; // represents string arguments which accumulate
//----------------------------------------------------------------------------------------------------
// Default hash/equals functions used by ResourceHashtable and KVHashtable
template<typename K> unsigned primitive_hash(const K& k) {
unsigned hash = (unsigned)((uintptr_t)k);
return hash ^ (hash >> 3); // just in case we're dealing with aligned ptrs
}
template<typename K> bool primitive_equals(const K& k0, const K& k1) {
return k0 == k1;
}
#endif // SHARE_VM_UTILITIES_GLOBALDEFINITIONS_HPP

View File

@ -65,6 +65,7 @@ template <MEMFLAGS F> BasicHashtableEntry<F>* BasicHashtable<F>::new_entry(unsig
len = 1 << log2_intptr(len); // round down to power of 2
assert(len >= _entry_size, "");
_first_free_entry = NEW_C_HEAP_ARRAY2(char, len, F, CURRENT_PC);
_entry_blocks->append(_first_free_entry);
_end_block = _first_free_entry + len;
}
entry = (BasicHashtableEntry<F>*)_first_free_entry;
@ -86,7 +87,9 @@ template <class T, MEMFLAGS F> HashtableEntry<T, F>* Hashtable<T, F>::new_entry(
}
// Version of hashtable entry allocation that allocates in the C heap directly.
// The allocator in blocks is preferable but doesn't have free semantics.
// The block allocator in BasicHashtable has less fragmentation, but the memory is not freed until
// the whole table is freed. Use allocate_new_entry() if you want to individually free the memory
// used by each entry
template <class T, MEMFLAGS F> HashtableEntry<T, F>* Hashtable<T, F>::allocate_new_entry(unsigned int hashValue, T obj) {
HashtableEntry<T, F>* entry = (HashtableEntry<T, F>*) NEW_C_HEAP_ARRAY(char, this->entry_size(), F);
@ -203,6 +206,20 @@ template <MEMFLAGS F> bool BasicHashtable<F>::resize(int new_size) {
return true;
}
template <MEMFLAGS F> bool BasicHashtable<F>::maybe_grow(int max_size, int load_factor) {
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
if (table_size() >= max_size) {
return false;
}
if (number_of_entries() / table_size() > load_factor) {
resize(MIN2<int>(table_size() * 2, max_size));
return true;
} else {
return false;
}
}
// Dump footprint and bucket length statistics
//
// Note: if you create a new subclass of Hashtable<MyNewType, F>, you will need to

View File

@ -25,11 +25,11 @@
#ifndef SHARE_VM_UTILITIES_HASHTABLE_HPP
#define SHARE_VM_UTILITIES_HASHTABLE_HPP
#include "classfile/classLoaderData.hpp"
#include "memory/allocation.hpp"
#include "oops/oop.hpp"
#include "oops/symbol.hpp"
#include "runtime/handles.hpp"
#include "utilities/growableArray.hpp"
// This is a generic hashtable, designed to be used for the symbol
// and string tables.
@ -146,6 +146,7 @@ public:
BasicHashtable(int table_size, int entry_size);
BasicHashtable(int table_size, int entry_size,
HashtableBucket<F>* buckets, int number_of_entries);
~BasicHashtable();
// Bucket handling
int hash_to_index(unsigned int full_hash) const {
@ -163,6 +164,7 @@ private:
char* _end_block;
int _entry_size;
volatile int _number_of_entries;
GrowableArray<char*>* _entry_blocks;
protected:
@ -233,6 +235,9 @@ public:
bool resize(int new_size);
// Grow the number of buckets if the average entries per bucket is over the load_factor
bool maybe_grow(int max_size, int load_factor = 8);
template <class T> void verify_table(const char* table_name) PRODUCT_RETURN;
};
@ -279,4 +284,55 @@ public:
}
};
// A subclass of BasicHashtable that allows you to do a simple K -> V mapping
// without using tons of boilerplate code.
template<
typename K, typename V, MEMFLAGS F,
unsigned (*HASH) (K const&) = primitive_hash<K>,
bool (*EQUALS)(K const&, K const&) = primitive_equals<K>
>
class KVHashtable : public BasicHashtable<F> {
class KVHashtableEntry : public BasicHashtableEntry<F> {
public:
K _key;
V _value;
KVHashtableEntry* next() {
return (KVHashtableEntry*)BasicHashtableEntry<F>::next();
}
};
protected:
KVHashtableEntry* bucket(int i) const {
return (KVHashtableEntry*)BasicHashtable<F>::bucket(i);
}
KVHashtableEntry* new_entry(unsigned int hashValue, K key, V value) {
KVHashtableEntry* entry = (KVHashtableEntry*)BasicHashtable<F>::new_entry(hashValue);
entry->_key = key;
entry->_value = value;
return entry;
}
public:
KVHashtable(int table_size) : BasicHashtable<F>(table_size, sizeof(KVHashtableEntry)) {}
void add(K key, V value) {
unsigned int hash = HASH(key);
KVHashtableEntry* entry = new_entry(hash, key, value);
BasicHashtable<F>::add_entry(BasicHashtable<F>::hash_to_index(hash), entry);
}
V* lookup(K key) {
unsigned int hash = HASH(key);
int index = BasicHashtable<F>::hash_to_index(hash);
for (KVHashtableEntry* e = bucket(index); e != NULL; e = e->next()) {
if (e->hash() == hash && e->_key == key) {
return &(e->_value);
}
}
return NULL;
}
};
#endif // SHARE_VM_UTILITIES_HASHTABLE_HPP

View File

@ -54,6 +54,13 @@ template <MEMFLAGS F> inline BasicHashtable<F>::BasicHashtable(int table_size, i
_buckets = buckets;
}
template <MEMFLAGS F> inline BasicHashtable<F>::~BasicHashtable() {
for (int i = 0; i < _entry_blocks->length(); i++) {
FREE_C_HEAP_ARRAY(char, _entry_blocks->at(i));
}
delete _entry_blocks;
free_buckets();
}
template <MEMFLAGS F> inline void BasicHashtable<F>::initialize(int table_size, int entry_size,
int number_of_entries) {
@ -64,6 +71,7 @@ template <MEMFLAGS F> inline void BasicHashtable<F>::initialize(int table_size,
_first_free_entry = NULL;
_end_block = NULL;
_number_of_entries = number_of_entries;
_entry_blocks = new(ResourceObj::C_HEAP, F) GrowableArray<char*>(4, true, F);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -32,15 +32,6 @@ template<typename K> struct ResourceHashtableFns {
typedef bool (*equals_fn)(K const&, K const&);
};
template<typename K> unsigned primitive_hash(const K& k) {
unsigned hash = (unsigned)((uintptr_t)k);
return hash ^ (hash >> 3); // just in case we're dealing with aligned ptrs
}
template<typename K> bool primitive_equals(const K& k0, const K& k1) {
return k0 == k1;
}
template<
typename K, typename V,
// xlC does not compile this:

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -29,17 +29,8 @@ import jdk.test.lib.cds.CDSOptions;
public class AppCDSOptions extends CDSOptions {
public String appJar;
// Application classes to be archived
public String[] appClasses;
public AppCDSOptions setAppJar(String appJar) {
this.appJar = appJar;
return this;
}
public AppCDSOptions setAppClasses(String[] appClasses) {
this.appClasses = appClasses;
return this;
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.cds.CDSOptions;
import jdk.test.lib.process.OutputAnalyzer;
/*
* @test
* @summary Try to archive lots of classes by searching for classes from the jrt:/ file system. With JDK 12
* this will produce an archive with over 30,000 classes.
* @requires vm.cds
* @library /test/lib
* @run driver/timeout=500 LotsOfClasses
*/
public class LotsOfClasses {
static Pattern pattern;
public static void main(String[] args) throws Throwable {
ArrayList<String> list = new ArrayList<>();
findAllClasses(list);
CDSOptions opts = new CDSOptions();
opts.setClassList(list);
opts.addSuffix("--add-modules");
opts.addSuffix("ALL-SYSTEM");
opts.addSuffix("-Xlog:hashtables");
OutputAnalyzer out = CDSTestUtils.createArchive(opts);
CDSTestUtils.checkDump(out);
}
static void findAllClasses(ArrayList<String> list) throws Throwable {
// Find all the classes in the jrt file system
pattern = Pattern.compile("/modules/[a-z.]*[a-z]+/([^-]*)[.]class");
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
Path base = fs.getPath("/modules/");
find(base, list);
}
static void find(Path p, ArrayList<String> list) throws Throwable {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) {
for (Path entry: stream) {
Matcher matcher = pattern.matcher(entry.toString());
if (matcher.find()) {
String className = matcher.group(1);
list.add(className);
//System.out.println(className);
}
try {
find(entry, list);
} catch (Throwable t) {}
}
}
}
}

View File

@ -95,17 +95,17 @@ public class TestCommon extends CDSTestUtils {
// Create AppCDS archive using most common args - convenience method
// Legacy name preserved for compatibility
public static OutputAnalyzer dump(String appJar, String appClasses[],
public static OutputAnalyzer dump(String appJar, String classList[],
String... suffix) throws Exception {
return createArchive(appJar, appClasses, suffix);
return createArchive(appJar, classList, suffix);
}
// Create AppCDS archive using most common args - convenience method
public static OutputAnalyzer createArchive(String appJar, String appClasses[],
public static OutputAnalyzer createArchive(String appJar, String classList[],
String... suffix) throws Exception {
AppCDSOptions opts = (new AppCDSOptions()).setAppJar(appJar)
.setAppClasses(appClasses);
AppCDSOptions opts = (new AppCDSOptions()).setAppJar(appJar);
opts.setClassList(classList);
opts.addSuffix(suffix);
return createArchive(opts);
}
@ -115,7 +115,6 @@ public class TestCommon extends CDSTestUtils {
throws Exception {
ArrayList<String> cmd = new ArrayList<String>();
File classList = makeClassList(opts.appClasses);
startNewArchiveName();
for (String p : opts.prefix) cmd.add(p);
@ -129,13 +128,17 @@ public class TestCommon extends CDSTestUtils {
}
cmd.add("-Xshare:dump");
cmd.add("-XX:ExtraSharedClassListFile=" + classList.getPath());
if (opts.archiveName == null)
opts.archiveName = getCurrentArchiveName();
cmd.add("-XX:SharedArchiveFile=" + opts.archiveName);
if (opts.classList != null) {
File classListFile = makeClassList(opts.classList);
cmd.add("-XX:ExtraSharedClassListFile=" + classListFile.getPath());
}
for (String s : opts.suffix) cmd.add(s);
String[] cmdLine = cmd.toArray(new String[cmd.size()]);
@ -235,9 +238,9 @@ public class TestCommon extends CDSTestUtils {
// A common operation: dump, then check results
public static OutputAnalyzer testDump(String appJar, String appClasses[],
public static OutputAnalyzer testDump(String appJar, String classList[],
String... suffix) throws Exception {
OutputAnalyzer output = dump(appJar, appClasses, suffix);
OutputAnalyzer output = dump(appJar, classList, suffix);
output.shouldContain("Loading classes to share");
output.shouldHaveExitValue(0);
return output;
@ -245,11 +248,11 @@ public class TestCommon extends CDSTestUtils {
/**
* Simple test -- dump and execute appJar with the given appClasses in classlist.
* Simple test -- dump and execute appJar with the given classList in classlist.
*/
public static OutputAnalyzer test(String appJar, String appClasses[], String... args)
public static OutputAnalyzer test(String appJar, String classList[], String... args)
throws Exception {
testDump(appJar, appClasses);
testDump(appJar, classList);
OutputAnalyzer output = exec(appJar, args);
return checkExec(output);

View File

@ -33,6 +33,9 @@ public class CDSOptions {
public ArrayList<String> suffix = new ArrayList<String>();
public boolean useSystemArchive = false;
// classes to be archived
public String[] classList;
// Indicate whether to append "-version" when using CDS Archive.
// Most of tests will use '-version'
public boolean useVersion = true;
@ -74,4 +77,16 @@ public class CDSOptions {
this.useSystemArchive = use;
return this;
}
public CDSOptions setClassList(String[] list) {
this.classList = list;
return this;
}
public CDSOptions setClassList(ArrayList<String> list) {
String array[] = new String[list.size()];
list.toArray(array);
this.classList = array;
return this;
}
}

View File

@ -248,6 +248,11 @@ public class CDSTestUtils {
opts.archiveName = getDefaultArchiveName();
cmd.add("-XX:SharedArchiveFile=./" + opts.archiveName);
if (opts.classList != null) {
File classListFile = makeClassList(opts.classList);
cmd.add("-XX:ExtraSharedClassListFile=" + classListFile.getPath());
}
for (String s : opts.suffix) cmd.add(s);
String[] cmdLine = cmd.toArray(new String[cmd.size()]);