diff a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp
--- a/src/hotspot/share/cds/dynamicArchive.cpp
+++ b/src/hotspot/share/cds/dynamicArchive.cpp
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2024, 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.
@@ -27,12 +27,15 @@
 #include "cds/archiveHeapWriter.hpp"
 #include "cds/archiveUtils.inline.hpp"
 #include "cds/cds_globals.hpp"
 #include "cds/cdsConfig.hpp"
 #include "cds/classPrelinker.hpp"
+#include "cds/classPreloader.hpp"
 #include "cds/dynamicArchive.hpp"
+#include "cds/metaspaceShared.hpp"
 #include "cds/regeneratedClasses.hpp"
+#include "classfile/classLoader.hpp"
 #include "classfile/classLoaderData.inline.hpp"
 #include "classfile/symbolTable.hpp"
 #include "classfile/systemDictionaryShared.hpp"
 #include "classfile/vmSymbols.hpp"
 #include "gc/shared/collectedHeap.hpp"
@@ -116,10 +119,13 @@
     if (SystemDictionaryShared::is_dumptime_table_empty()) {
       log_warning(cds, dynamic)("There is no class to be included in the dynamic archive.");
       return;
     }
 
+    log_info(cds,dynamic)("CDS dynamic dump: clinit = %ldms)",
+                          ClassLoader::class_init_time_ms());
+
     init_header();
     gather_source_objs();
     gather_array_klasses();
     reserve_buffer();
 
@@ -129,10 +135,20 @@
     dump_ro_metadata();
     relocate_metaspaceobj_embedded_pointers();
 
     verify_estimate_size(_estimated_metaspaceobj_bytes, "MetaspaceObjs");
 
+    sort_methods();
+
+    {
+      ArchiveBuilder::OtherROAllocMark mark;
+      ClassPreloader::record_preloaded_classes(false);
+    }
+
+    log_info(cds)("Make classes shareable");
+    make_klasses_shareable();
+
     char* serialized_data;
     {
       // Write the symbol table and system dictionaries to the RO space.
       // Note that these tables still point to the *original* objects, so
       // they would need to call DynamicArchive::original_to_target() to
@@ -140,27 +156,31 @@
       assert(current_dump_region() == ro_region(), "Must be RO space");
       SymbolTable::write_to_archive(symbols());
 
       ArchiveBuilder::OtherROAllocMark mark;
       SystemDictionaryShared::write_to_archive(false);
+
       DynamicArchive::dump_array_klasses();
+      ClassPreloader::record_initiated_classes(false);
+      TrainingData::dump_training_data();
 
       serialized_data = ro_region()->top();
       WriteClosure wc(ro_region());
       ArchiveBuilder::serialize_dynamic_archivable_items(&wc);
     }
 
     verify_estimate_size(_estimated_hashtable_bytes, "Hashtables");
 
-    sort_methods();
-
-    log_info(cds)("Make classes shareable");
-    make_klasses_shareable();
-
     log_info(cds)("Adjust lambda proxy class dictionary");
     SystemDictionaryShared::adjust_lambda_proxy_class_dictionary();
 
+    log_info(cds)("Adjust method info dictionary");
+    SystemDictionaryShared::adjust_method_info_dictionary();
+
+    log_info(cds)("Make training data shareable");
+    make_training_data_shareable();
+
     relocate_to_requested();
 
     write_archive(serialized_data);
     release_header();
     DynamicArchive::post_dump();
@@ -172,10 +192,11 @@
   }
 
   virtual void iterate_roots(MetaspaceClosure* it) {
     FileMapInfo::metaspace_pointers_do(it);
     SystemDictionaryShared::dumptime_classes_do(it);
+    TrainingData::iterate_roots(it);
     iterate_primitive_array_klasses(it);
   }
 
   void iterate_primitive_array_klasses(MetaspaceClosure* it) {
     for (int i = T_BOOLEAN; i <= T_LONG; i++) {
@@ -228,10 +249,11 @@
   _header = nullptr;
 }
 
 void DynamicArchiveBuilder::post_dump() {
   ArchivePtrMarker::reset_map_and_vs();
+  ClassPreloader::dispose();
   ClassPrelinker::dispose();
 }
 
 void DynamicArchiveBuilder::sort_methods() {
   InstanceKlass::disable_method_binary_search();
@@ -499,13 +521,22 @@
   }
 
   log_info(cds, dynamic)("Preparing for dynamic dump at exit in thread %s", current->name());
 
   JavaThread* THREAD = current; // For TRAPS processing related to link_shared_classes
+
+  {
+    // FIXME-HACK - make sure we have at least one class in the dynamic archive
+    TempNewSymbol class_name = SymbolTable::new_symbol("sun/nio/cs/IBM850"); // unusual class; shouldn't be used by our tests cases.
+    SystemDictionary::resolve_or_null(class_name, Handle(), Handle(), THREAD);
+    guarantee(!HAS_PENDING_EXCEPTION, "must have this class");
+  }
+
   MetaspaceShared::link_shared_classes(false/*not from jcmd*/, THREAD);
   if (!HAS_PENDING_EXCEPTION) {
     // copy shared path table to saved.
+    TrainingData::init_dumptime_table(CHECK); // captures TrainingDataSetLocker
     if (!HAS_PENDING_EXCEPTION) {
       VM_PopulateDynamicDumpSharedSpace op(archive_name);
       VMThread::execute(&op);
       return;
     }
@@ -525,10 +556,11 @@
   assert(UseSharedSpaces && RecordDynamicDumpInfo, "already checked in arguments.cpp");
   assert(ArchiveClassesAtExit == nullptr, "already checked in arguments.cpp");
   assert(CDSConfig::is_dumping_dynamic_archive(), "already checked by check_for_dynamic_dump() during VM startup");
   MetaspaceShared::link_shared_classes(true/*from jcmd*/, CHECK);
   // copy shared path table to saved.
+  TrainingData::init_dumptime_table(CHECK); // captures TrainingDataSetLocker
   VM_PopulateDynamicDumpSharedSpace op(archive_name);
   VMThread::execute(&op);
 }
 
 bool DynamicArchive::validate(FileMapInfo* dynamic_info) {
