8213587: Speed up CDS dump time by using resizable hashtables
Reviewed-by: jiangli, coleenp, gziemski
--- a/src/hotspot/share/classfile/classListParser.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/classfile/classListParser.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -389,8 +389,8 @@
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 @@
}
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;
}
--- a/src/hotspot/share/classfile/classListParser.hpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/classfile/classListParser.hpp Tue Nov 20 20:00:15 2018 -0800
@@ -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 {
--- a/src/hotspot/share/classfile/classLoaderExt.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/classfile/classLoaderExt.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -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 @@
// 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);
--- a/src/hotspot/share/classfile/compactHashtable.hpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/classfile/compactHashtable.hpp Tue Nov 20 20:00:15 2018 -0800
@@ -27,7 +27,7 @@
#include "oops/array.hpp"
#include "oops/symbol.hpp"
-#include "utilities/hashtable.hpp"
+#include "utilities/growableArray.hpp"
template <
--- a/src/hotspot/share/classfile/dictionary.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/classfile/dictionary.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -67,7 +67,6 @@
}
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) {
--- a/src/hotspot/share/classfile/moduleEntry.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/classfile/moduleEntry.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -352,7 +352,6 @@
}
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,
--- a/src/hotspot/share/classfile/packageEntry.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/classfile/packageEntry.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -191,7 +191,6 @@
}
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) {
--- a/src/hotspot/share/gc/g1/g1CodeCacheRemSet.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/gc/g1/g1CodeCacheRemSet.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -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 @@
}
}
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);
}
--- a/src/hotspot/share/memory/metaspaceClosure.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/memory/metaspaceClosure.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -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 @@
}
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.
}
--- a/src/hotspot/share/memory/metaspaceClosure.hpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/memory/metaspaceClosure.hpp Tue Nov 20 20:00:15 2018 -0800
@@ -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 @@
// 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
--- a/src/hotspot/share/memory/metaspaceShared.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/memory/metaspaceShared.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -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 @@
// 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 @@
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 @@
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");
--- a/src/hotspot/share/utilities/globalDefinitions.hpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/utilities/globalDefinitions.hpp Tue Nov 20 20:00:15 2018 -0800
@@ -1261,4 +1261,17 @@
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
--- a/src/hotspot/share/utilities/hashtable.cpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/utilities/hashtable.cpp Tue Nov 20 20:00:15 2018 -0800
@@ -65,6 +65,7 @@
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 @@
}
// 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 @@
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
--- a/src/hotspot/share/utilities/hashtable.hpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/utilities/hashtable.hpp Tue Nov 20 20:00:15 2018 -0800
@@ -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 @@
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 @@
char* _end_block;
int _entry_size;
volatile int _number_of_entries;
+ GrowableArray<char*>* _entry_blocks;
protected:
@@ -233,6 +235,9 @@
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 @@
}
};
+// 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
--- a/src/hotspot/share/utilities/hashtable.inline.hpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/utilities/hashtable.inline.hpp Tue Nov 20 20:00:15 2018 -0800
@@ -54,6 +54,13 @@
_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 @@
_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);
}
--- a/src/hotspot/share/utilities/resourceHash.hpp Tue Nov 20 18:36:57 2018 -0800
+++ b/src/hotspot/share/utilities/resourceHash.hpp Tue Nov 20 20:00:15 2018 -0800
@@ -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 @@
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:
--- a/test/hotspot/jtreg/runtime/appcds/AppCDSOptions.java Tue Nov 20 18:36:57 2018 -0800
+++ b/test/hotspot/jtreg/runtime/appcds/AppCDSOptions.java Tue Nov 20 20:00:15 2018 -0800
@@ -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 @@
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;
- }
-
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/appcds/LotsOfClasses.java Tue Nov 20 20:00:15 2018 -0800
@@ -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) {}
+ }
+ }
+ }
+}
--- a/test/hotspot/jtreg/runtime/appcds/TestCommon.java Tue Nov 20 18:36:57 2018 -0800
+++ b/test/hotspot/jtreg/runtime/appcds/TestCommon.java Tue Nov 20 20:00:15 2018 -0800
@@ -95,17 +95,17 @@
// 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 @@
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 @@
}
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 @@
// 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 @@
/**
- * 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);
--- a/test/lib/jdk/test/lib/cds/CDSOptions.java Tue Nov 20 18:36:57 2018 -0800
+++ b/test/lib/jdk/test/lib/cds/CDSOptions.java Tue Nov 20 20:00:15 2018 -0800
@@ -33,6 +33,9 @@
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 @@
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;
+ }
+
}
--- a/test/lib/jdk/test/lib/cds/CDSTestUtils.java Tue Nov 20 18:36:57 2018 -0800
+++ b/test/lib/jdk/test/lib/cds/CDSTestUtils.java Tue Nov 20 20:00:15 2018 -0800
@@ -248,6 +248,11 @@
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()]);