Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a57bd2f
Support custom loaders
ashu-mehra Jan 18, 2026
78a9d39
Handle aot safe custom loaders separate from builtin loader
ashu-mehra Jan 20, 2026
0a78289
Prelinking of classes loaded by aot-safe custom loaders
ashu-mehra Jan 22, 2026
1e6e866
Use CDS$UnregisteredClassLoader to load aot-safe custom loader classe…
ashu-mehra Jan 28, 2026
a83dd49
Remove code for archiving packages and modules tables for custom loaders
ashu-mehra Jan 29, 2026
d5973b1
Prelink classes loaded by aot-safe custom loaders
ashu-mehra Jan 29, 2026
157f0e7
Preload classes for aot-safe custom loaders when they are constructed
ashu-mehra Feb 2, 2026
4ecc907
Add support for URLClassLoader
ashu-mehra Feb 5, 2026
71407bd
Cleanup
ashu-mehra Feb 6, 2026
f89973e
Add validation of classpath for URLClassLoaders
ashu-mehra Feb 6, 2026
d5c2f06
Add missing file
ashu-mehra Feb 10, 2026
76a06f1
Merge branch 'premain' into custom-loader-support-v2
ashu-mehra Feb 11, 2026
24c84ae
Fix bugs
ashu-mehra Feb 11, 2026
da243b7
Keep aot-safe custom loaders alive
ashu-mehra Feb 11, 2026
7fe29c6
Keep aot-safe custom loaders alive during training run
ashu-mehra Feb 11, 2026
4711732
Use SystemDictionary::preload_class to load classes in assembly phase
ashu-mehra Feb 18, 2026
e9fafef
Some cleanup
ashu-mehra Feb 18, 2026
d3eeebc
Revert 7fe29c65435154c0da2dbcfaeb172896f48e5773
ashu-mehra Feb 18, 2026
0d29c8b
Remove unused code
ashu-mehra Feb 18, 2026
a5d31aa
Remove whitespace
ashu-mehra Feb 18, 2026
5bca9fa
Skip archiving URLClassLoader instance classpath if it has not loaded…
ashu-mehra Feb 19, 2026
3f5670f
Move code for creating custom loader specific class list to a separate
ashu-mehra Feb 19, 2026
12cc3f1
Store aot-safe classes in a map in FinalImageRecipes
ashu-mehra Feb 25, 2026
db91bc8
Handle multipe instances of URLClassLoader with same classpath
ashu-mehra Feb 25, 2026
eab1a7b
Fix compile failure
ashu-mehra Feb 25, 2026
61247fc
Fix bug in setting aot identity
ashu-mehra Feb 26, 2026
c17f50b
Add some tests
ashu-mehra Feb 26, 2026
67278a3
Fix bugs, update tests
ashu-mehra Mar 9, 2026
77930e3
Add missing test files
ashu-mehra Mar 9, 2026
d57711f
Fix whitespace erros
ashu-mehra Mar 9, 2026
053538f
Merge branch 'premain' into custom-loader-support-v2
ashu-mehra Mar 9, 2026
1e35626
Fix bug after merge
ashu-mehra Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/hotspot/share/cds/aotArtifactFinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ void AOTArtifactFinder::add_cached_instance_class(InstanceKlass* ik) {
add_cached_instance_class(nest_host);
}

if (CDSConfig::is_dumping_final_static_archive() && ik->defined_by_other_loaders()) {
if (CDSConfig::is_dumping_final_static_archive() && ik->defined_by_other_loaders() && !ik->is_defined_by_aot_safe_custom_loader() ) {
// The following are not appliable to unregistered classes
return;
}
Expand Down
45 changes: 44 additions & 1 deletion src/hotspot/share/cds/aotClassLinker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "cds/archiveBuilder.hpp"
#include "cds/archiveUtils.inline.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/customLoaderSupport.hpp"
#include "cds/heapShared.hpp"
#include "cds/lambdaFormInvokers.inline.hpp"
#include "classfile/classLoader.hpp"
Expand All @@ -45,6 +46,12 @@ AOTClassLinker::ClassesTable* AOTClassLinker::_vm_classes = nullptr;
AOTClassLinker::ClassesTable* AOTClassLinker::_candidates = nullptr;
GrowableArrayCHeap<InstanceKlass*, mtClassShared>* AOTClassLinker::_sorted_candidates = nullptr;

static const unsigned INITIAL_TABLE_SIZE = 997; // prime number
static const unsigned MAX_TABLE_SIZE = 10000;

ClassLoaderIdToClassTableMap * _custom_loader_prelinked_table;
ArchivedCustomLoaderClassTableMap _archived_custom_loader_prelinked_classes_map;

#ifdef ASSERT
bool AOTClassLinker::is_initialized() {
assert(CDSConfig::is_dumping_archive(), "AOTClassLinker is for CDS dumping only");
Expand All @@ -59,6 +66,8 @@ void AOTClassLinker::initialize() {
_candidates = new (mtClass)ClassesTable();
_sorted_candidates = new GrowableArrayCHeap<InstanceKlass*, mtClassShared>(1000);

_custom_loader_prelinked_table = new (mtClass) ClassLoaderIdToClassTableMap(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE);

for (auto id : EnumRange<vmClassID>{}) {
add_vm_class(vmClasses::klass_at(id));
}
Expand Down Expand Up @@ -113,6 +122,11 @@ void AOTClassLinker::add_new_candidate(InstanceKlass* ik) {
_candidates->put_when_absent(ik, true);
_sorted_candidates->append(ik);

Symbol* loader_id;
loader_id = ik->cl_aot_identity();
if (loader_id != nullptr) {
_custom_loader_prelinked_table->add_class(loader_id, ik);
}
if (log_is_enabled(Info, aot, link)) {
ResourceMark rm;
log_info(aot, link)("%s %s %p", class_category_name(ik), ik->external_name(), ik);
Expand All @@ -127,7 +141,7 @@ bool AOTClassLinker::try_add_candidate(InstanceKlass* ik) {
assert(is_initialized(), "sanity");
assert(CDSConfig::is_dumping_aot_linked_classes(), "sanity");

if (!SystemDictionaryShared::is_builtin(ik)) {
if (!SystemDictionaryShared::is_builtin(ik) && !ik->is_defined_by_aot_safe_custom_loader()) {
// not loaded by a class loader which we know about
return false;
}
Expand Down Expand Up @@ -190,6 +204,20 @@ void AOTClassLinker::add_candidates() {
}
}

ArchivedCustomLoaderClassTable* AOTClassLinker::get_archived_prelinked_table(Symbol* aot_id) {
return _archived_custom_loader_prelinked_classes_map.get_class_list(aot_id);
}

void AOTClassLinker::all_symbols_do(MetaspaceClosure* it) {
_custom_loader_prelinked_table->iterate_all([&](Symbol*& loader_id, ClassList*& class_list) {
it->push(&loader_id);
});
}

void AOTClassLinker::serialize_prelinked_classes_map_header(SerializeClosure* soc) {
_archived_custom_loader_prelinked_classes_map.serialize_header(soc);
}

void AOTClassLinker::write_to_archive() {
assert(is_initialized(), "sanity");
assert_at_safepoint();
Expand All @@ -200,6 +228,19 @@ void AOTClassLinker::write_to_archive() {
table->set_boot2(write_classes(nullptr, false));
table->set_platform(write_classes(SystemDictionary::java_platform_loader(), false));
table->set_app(write_classes(SystemDictionary::java_system_loader(), false));

_custom_loader_prelinked_table->write_to_archive(&_archived_custom_loader_prelinked_classes_map, "archived prelinked table");

if (log_is_enabled(Info, aot, link)) {
ResourceMark rm;
_custom_loader_prelinked_table->iterate_all([&](Symbol* loader_id, ClassList* class_list) {
log_info(aot, link)("Class loader \"%s\" has %d classes in prelinked table", loader_id->as_C_string(), class_list->length());
for (int i = 0; i < class_list->length(); i++) {
InstanceKlass* ik = class_list->at(i);
log_info(aot, link)(" %s", ik->external_name());
}
});
}
}
}

Expand Down Expand Up @@ -281,6 +322,8 @@ const char* AOTClassLinker::class_category_name(Klass* k) {
return "plat";
} else if (loader == SystemDictionary::java_system_loader()) {
return "app";
} else if (k->cl_aot_identity() != nullptr) {
return "aotsafe_custom_lodaer";
} else {
return "unreg";
}
Expand Down
5 changes: 5 additions & 0 deletions src/hotspot/share/cds/aotClassLinker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "utilities/macros.hpp"

class AOTLinkedClassTable;
class ArchivedCustomLoaderClassTable;
class InstanceKlass;
class SerializeClosure;
template <typename T> class Array;
Expand Down Expand Up @@ -111,6 +112,10 @@ class AOTClassLinker : AllStatic {
static int num_app_initiated_classes();
static int num_platform_initiated_classes();

static void all_symbols_do(MetaspaceClosure* it);
static void serialize_prelinked_classes_map_header(SerializeClosure* soc);
static ArchivedCustomLoaderClassTable* get_archived_prelinked_table(Symbol* aot_id);

// Used in logging: "boot1", "boot2", "plat", "app" and "unreg";
static const char* class_category_name(AOTLinkedClassCategory category);
static const char* class_category_name(Klass* k);
Expand Down
143 changes: 143 additions & 0 deletions src/hotspot/share/cds/aotClassLocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@
#include "cds/aotLogging.hpp"
#include "cds/aotMetaspace.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/archiveUtils.inline.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/dynamicArchive.hpp"
#include "cds/filemap.hpp"
#include "cds/serializeClosure.hpp"
#include "classfile/classLoader.hpp"
#include "classfile/classLoaderData.hpp"
#include "classfile/javaClasses.hpp"
#include "classfile/compactHashtable.hpp"
#include "classfile/symbolTable.hpp"
#include "logging/log.hpp"
#include "logging/logStream.hpp"
#include "memory/metadataFactory.hpp"
Expand All @@ -41,8 +44,10 @@
#include "oops/array.hpp"
#include "oops/objArrayKlass.hpp"
#include "runtime/arguments.hpp"
#include "runtime/mutexLocker.hpp"
#include "utilities/classpathStream.hpp"
#include "utilities/formatBuffer.hpp"
#include "utilities/resizableHashTable.hpp"
#include "utilities/stringUtils.hpp"

#include <errno.h>
Expand Down Expand Up @@ -156,6 +161,13 @@ class AllClassLocationStreams {
}
};

class URLClassLoaderClassLocationStream : public ClassLocationStream {
public:
URLClassLoaderClassLocationStream(const char* classpath) : ClassLocationStream() {
add_paths_in_classpath(classpath);
}
};

static bool has_jar_suffix(const char* filename) {
// In jdk.internal.module.ModulePath.readModule(), it checks for the ".jar" suffix.
// Performing the same check here.
Expand Down Expand Up @@ -1095,3 +1107,134 @@ void AOTClassLocationConfig::print_on(outputStream* st) const {
st->print_cr("(%-6s) [%d] = %s", type, i, path);
}
}

typedef ResizeableHashTable<Symbol*, GrowableClassLocationArray*, AnyObj::C_HEAP, mtClass> AOTIdToURLLoaderClasspath;
static AOTIdToURLLoaderClasspath* _aot_id_to_classpath = nullptr;

static inline bool ucc_symbol_equals(URLClassLoaderClasspath* ucc, Symbol* loader_id, int len_unused) {
return ucc->loader_id()->equals(loader_id);
}

class ArchivedAOTIdToClasspathMap : public OffsetCompactHashtable<Symbol*, URLClassLoaderClasspath*,
ucc_symbol_equals> {};
static ArchivedAOTIdToClasspathMap _archived_aot_id_to_classpath;

void URLClassLoaderClasspathSupport::init() {
_aot_id_to_classpath = new (mtClass) AOTIdToURLLoaderClasspath(11, 1000);
if (CDSConfig::is_dumping_final_static_archive()) {
reload_runtime_map();
}
}

void URLClassLoaderClasspathSupport::reload_runtime_map() {
_archived_aot_id_to_classpath.iterate([&](URLClassLoaderClasspath* ucc) {
GrowableClassLocationArray* locations = new GrowableClassLocationArray(ucc->num_entries());
for (int i = 0; i < ucc->num_entries(); i++) {
locations->append(ucc->class_location_at(i));
log_info(class, path)("path [%d] = %s", i, ucc->class_location_at(i)->path());
}
_aot_id_to_classpath->put(ucc->loader_id(), locations);
return true;
});
}

bool URLClassLoaderClasspathSupport::add_urlclassloader_classpath(ClassLoaderData* loader_data, Symbol* aot_id, const char* classpath) {
assert(_aot_id_to_classpath != nullptr, "sanity check");
if (_aot_id_to_classpath->contains(aot_id)) {
// cannot allow aot_id clash; return without doing anything
return false;
}
GrowableClassLocationArray* locations = new GrowableClassLocationArray(10);
URLClassLoaderClassLocationStream css(classpath);
for (css.start(); css.has_next(); ) {
const char* path = css.get_next();
AOTClassLocation* cs = AOTClassLocation::allocate(JavaThread::current(), path, locations->length(), AOTClassLocation::Group::URLCLASSLOADER_CLASSPATH, false);
log_info(class, path)("path [%d] = %s", locations->length(), path);
locations->append(cs);
}
_aot_id_to_classpath->put(aot_id, locations);
return true;
}

class URLClassLoaderClasspathArchiver : StackObj {
private:
CompactHashtableWriter* _writer;
ArchiveBuilder* _builder;
public:
URLClassLoaderClasspathArchiver(CompactHashtableWriter* writer) : _writer(writer),
_builder(ArchiveBuilder::current())
{}

bool do_entry(Symbol* loader_id, GrowableClassLocationArray* class_locations) {
// If the loader_id has not been archived yet, it implies no class was loaded using this loader.
// So there is no point to archive classpath for this loader_id.
if (!_builder->has_been_archived(loader_id)) {
return true;
}
Array<AOTClassLocation*>* archived_copy = ArchiveBuilder::new_ro_array<AOTClassLocation*>(class_locations->length());
for (int i = 0; i < class_locations->length(); i++) {
archived_copy->at_put(i, class_locations->at(i)->write_to_archive());
ArchivePtrMarker::mark_pointer((address*)archived_copy->adr_at(i));
}
URLClassLoaderClasspath* ucc = (URLClassLoaderClasspath*)ArchiveBuilder::ro_region_alloc(sizeof(URLClassLoaderClasspath));
assert(_builder->has_been_archived(loader_id), "must be");
Symbol* buffered_sym = _builder->get_buffered_addr(loader_id);
ucc->init(buffered_sym, archived_copy);
ArchivePtrMarker::mark_pointer(ucc->loader_id_addr());
ArchivePtrMarker::mark_pointer(ucc->class_locations_addr());
unsigned int hash = Symbol::symbol_hash(loader_id);
_writer->add(hash, AOTCompressedPointers::encode_not_null((address)ucc));
return true;
}
};

void URLClassLoaderClasspathSupport::archive_classpath_map() {
CompactHashtableStats stats;
CompactHashtableWriter writer(_aot_id_to_classpath->number_of_entries(), &stats);
URLClassLoaderClasspathArchiver archiver(&writer);
_aot_id_to_classpath->iterate(&archiver);
writer.dump(&_archived_aot_id_to_classpath, "archived prelinked table");
}

void URLClassLoaderClasspathSupport::serialize_classpath_map_table_header(SerializeClosure* soc) {
_archived_aot_id_to_classpath.serialize_header(soc);
}

bool URLClassLoaderClasspathSupport::claim_and_verify_archived_classpath(ClassLoaderData* loader_data, Symbol* aot_id, const char* classpath) {
ResourceMark rm;
assert(aot_id != nullptr, "sanity check");
const char* aot_id_str = aot_id->as_C_string();
unsigned int hash = Symbol::symbol_hash(aot_id);
URLClassLoaderClasspath* archived_classpath = _archived_aot_id_to_classpath.lookup(aot_id, hash, /*len*/0); // len is ignored
if (archived_classpath == nullptr) {
aot_log_trace(aot)("No archived entry found for URLClassLoader (id=%s)", aot_id_str);
return false;
}

{
// Acquire lock before loader_data claims the archived_classpath
MutexLocker mu(URLClassLoaderClasspath_lock, Mutex::_no_safepoint_check_flag);
if (archived_classpath->cld_owner() != nullptr) {
aot_log_warning(aot)("Duplicate URLClassLoader with same classpath found");
return false;
}
archived_classpath->set_cld_owner(loader_data);
}

URLClassLoaderClassLocationStream uccs(classpath);
uccs.start();
for (int i = 0; i < archived_classpath->num_entries(); i++) {
AOTClassLocation* location = archived_classpath->class_location_at(i);
const char* archived_path = location->path();
if (!uccs.has_next()) {
aot_log_warning(aot)("URLClassLoader (id=%s) classpath validation failed (reason: classpath has fewer elements than expected)", aot_id_str);
return false;
}
const char* runtime_path = uccs.get_next();
if (!location->check(runtime_path, true)) {
aot_log_warning(aot)("URLClassLoader (id=%s) classpath validation failed", aot_id_str);
return false;
}
}
return true;
}
37 changes: 35 additions & 2 deletions src/hotspot/share/cds/aotClassLocation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ class AOTClassLocation {
MODULES_IMAGE,
BOOT_CLASSPATH,
APP_CLASSPATH,
MODULE_PATH
MODULE_PATH,
URLCLASSLOADER_CLASSPATH
};
private:
enum class FileType : int {
Expand Down Expand Up @@ -88,6 +89,7 @@ class AOTClassLocation {
static AOTClassLocation* allocate(JavaThread* current, const char* path, int index, Group group,
bool from_cpattr = false, bool is_jrt = false);

size_t path_length() const { return _path_length; }
size_t total_size() const { return manifest_offset() + _manifest_length + 1; }
const char* path() const { return ((const char*)this) + path_offset(); }
size_t manifest_length() const { return _manifest_length; }
Expand Down Expand Up @@ -115,6 +117,29 @@ class AOTClassLocation {
bool check(const char* runtime_path, bool has_aot_linked_classes) const;
};

using GrowableClassLocationArray = GrowableArrayCHeap<AOTClassLocation*, mtClassShared>;

class URLClassLoaderClasspath {
private:
Symbol* _loader_id;
Array<AOTClassLocation*>* _class_locations;
ClassLoaderData* _cld_owner;
public:
void init(Symbol* aot_id, Array<AOTClassLocation*>* class_locations) {
_loader_id = aot_id;
_class_locations = class_locations;
_cld_owner = nullptr;
}
Symbol* loader_id() const { return _loader_id; }
address* loader_id_addr() const { return (address*)&_loader_id; }
Array<AOTClassLocation*>* class_locations() const { return _class_locations; }
address* class_locations_addr() const { return (address*)&_class_locations; }
AOTClassLocation* class_location_at(int i) { return _class_locations->at(i); }
int num_entries() { return _class_locations->length(); }
ClassLoaderData* cld_owner() const { return _cld_owner; }
void set_cld_owner(ClassLoaderData* cld) { _cld_owner = cld; }
};

// AOTClassLocationConfig
//
// Keep track of the set of AOTClassLocations used when an AOTCache is created.
Expand All @@ -134,7 +159,6 @@ class AOTClassLocation {

class AOTClassLocationConfig : public CHeapObj<mtClassShared> {
using Group = AOTClassLocation::Group;
using GrowableClassLocationArray = GrowableArrayCHeap<AOTClassLocation*, mtClassShared>;

// Note: both of the following are non-null if we are dumping a dynamic archive.
static AOTClassLocationConfig* _dumptime_instance;
Expand Down Expand Up @@ -277,5 +301,14 @@ class AOTClassLocationConfig : public CHeapObj<mtClassShared> {
static void print();
};

class URLClassLoaderClasspathSupport : AllStatic {
public:
static void init();
static void reload_runtime_map();
static bool add_urlclassloader_classpath(ClassLoaderData* loader_data, Symbol* aot_id, const char* classpath);
static void archive_classpath_map();
static void serialize_classpath_map_table_header(SerializeClosure* soc);
static bool claim_and_verify_archived_classpath(ClassLoaderData* loader_data, Symbol* aot_id, const char* classpath);
};

#endif // SHARE_CDS_AOTCLASSLOCATION_HPP
Loading