8213587: Speed up CDS dump time by using resizable hashtables
authoriklam
Tue, 20 Nov 2018 20:00:15 -0800
changeset 52631 3009ca99de32
parent 52630 68d450652337
child 52632 1089e8fd8439
8213587: Speed up CDS dump time by using resizable hashtables Reviewed-by: jiangli, coleenp, gziemski
src/hotspot/share/classfile/classListParser.cpp
src/hotspot/share/classfile/classListParser.hpp
src/hotspot/share/classfile/classLoaderExt.cpp
src/hotspot/share/classfile/compactHashtable.hpp
src/hotspot/share/classfile/dictionary.cpp
src/hotspot/share/classfile/moduleEntry.cpp
src/hotspot/share/classfile/packageEntry.cpp
src/hotspot/share/gc/g1/g1CodeCacheRemSet.cpp
src/hotspot/share/memory/metaspaceClosure.cpp
src/hotspot/share/memory/metaspaceClosure.hpp
src/hotspot/share/memory/metaspaceShared.cpp
src/hotspot/share/utilities/globalDefinitions.hpp
src/hotspot/share/utilities/hashtable.cpp
src/hotspot/share/utilities/hashtable.hpp
src/hotspot/share/utilities/hashtable.inline.hpp
src/hotspot/share/utilities/resourceHash.hpp
test/hotspot/jtreg/runtime/appcds/AppCDSOptions.java
test/hotspot/jtreg/runtime/appcds/LotsOfClasses.java
test/hotspot/jtreg/runtime/appcds/TestCommon.java
test/lib/jdk/test/lib/cds/CDSOptions.java
test/lib/jdk/test/lib/cds/CDSTestUtils.java
--- 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()]);