8348426: Generate binary file for -XX:AOTMode=record -XX:AOTConfiguration=file

Reviewed-by: ccheung, asmehra, kvn, iveresov
This commit is contained in:
Ioi Lam 2025-02-25 22:56:25 +00:00
parent 267d69bed6
commit 86024ebdb0
42 changed files with 1185 additions and 218 deletions

View File

@ -989,8 +989,14 @@ bool AOTClassLocationConfig::validate(bool has_aot_linked_classes, bool* has_ext
const char* hint_msg = log_is_enabled(Info, class, path) ?
"" : " (hint: enable -Xlog:class+path=info to diagnose the failure)";
if (RequireSharedSpaces && !PrintSharedArchiveAndExit) {
log_error(cds)("%s%s", mismatch_msg, hint_msg);
MetaspaceShared::unrecoverable_loading_error();
if (CDSConfig::is_dumping_final_static_archive()) {
log_error(cds)("class path and/or module path are not compatible with the "
"ones specified when the AOTConfiguration file was recorded%s", hint_msg);
vm_exit_during_initialization("Unable to use create AOT cache.", nullptr);
} else {
log_error(cds)("%s%s", mismatch_msg, hint_msg);
MetaspaceShared::unrecoverable_loading_error();
}
} else {
log_warning(cds)("%s%s", mismatch_msg, hint_msg);
}

View File

@ -507,9 +507,8 @@ bool ArchiveBuilder::is_excluded(Klass* klass) {
return SystemDictionaryShared::is_excluded_class(ik);
} else if (klass->is_objArray_klass()) {
Klass* bottom = ObjArrayKlass::cast(klass)->bottom_klass();
if (MetaspaceShared::is_shared_static(bottom)) {
if (CDSConfig::is_dumping_dynamic_archive() && MetaspaceShared::is_shared_static(bottom)) {
// The bottom class is in the static archive so it's clearly not excluded.
assert(CDSConfig::is_dumping_dynamic_archive(), "sanity");
return false;
} else if (bottom->is_instance_klass()) {
return SystemDictionaryShared::is_excluded_class(InstanceKlass::cast(bottom));
@ -521,7 +520,7 @@ bool ArchiveBuilder::is_excluded(Klass* klass) {
ArchiveBuilder::FollowMode ArchiveBuilder::get_follow_mode(MetaspaceClosure::Ref *ref) {
address obj = ref->obj();
if (MetaspaceShared::is_in_shared_metaspace(obj)) {
if (CDSConfig::is_dumping_dynamic_archive() && MetaspaceShared::is_in_shared_metaspace(obj)) {
// Don't dump existing shared metadata again.
return point_to_it;
} else if (ref->msotype() == MetaspaceObj::MethodDataType ||

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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
@ -255,11 +255,23 @@ public:
};
class ArchiveUtils {
template <typename T> static Array<T>* archive_non_ptr_array(GrowableArray<T>* tmp_array);
template <typename T> static Array<T>* archive_ptr_array(GrowableArray<T>* tmp_array);
public:
static const uintx MAX_SHARED_DELTA = 0x7FFFFFFF;
static void log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) NOT_CDS_RETURN;
static bool has_aot_initialized_mirror(InstanceKlass* src_ik);
template <typename T> static Array<T>* archive_array(GrowableArray<T>* tmp_array);
template <typename T, ENABLE_IF(!std::is_pointer<T>::value)>
static Array<T>* archive_array(GrowableArray<T>* tmp_array) {
return archive_non_ptr_array(tmp_array);
}
template <typename T, ENABLE_IF(std::is_pointer<T>::value)>
static Array<T>* archive_array(GrowableArray<T>* tmp_array) {
return archive_ptr_array(tmp_array);
}
// The following functions translate between a u4 offset and an address in the
// the range of the mapped CDS archive (e.g., Metaspace::is_in_shared_metaspace()).

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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
@ -28,6 +28,8 @@
#include "cds/archiveUtils.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/metaspaceShared.hpp"
#include "oops/array.hpp"
#include "utilities/bitMap.inline.hpp"
#include "utilities/growableArray.hpp"
@ -52,13 +54,40 @@ inline bool SharedDataRelocator::do_bit(size_t offset) {
// Returns the address of an Array<T> that's allocated in the ArchiveBuilder "buffer" space.
template <typename T>
Array<T>* ArchiveUtils::archive_array(GrowableArray<T>* tmp_array) {
Array<T>* ArchiveUtils::archive_non_ptr_array(GrowableArray<T>* tmp_array) {
ArchiveBuilder* builder = ArchiveBuilder::current();
Array<T>* archived_array = ArchiveBuilder::new_ro_array<T>(tmp_array->length());
for (int i = 0; i < tmp_array->length(); i++) {
archived_array->at_put(i, tmp_array->at(i));
if (std::is_pointer<T>::value) {
}
return archived_array;
}
// Returns the address of an Array<T> that's allocated in the ArchiveBuilder "buffer" space.
// All pointers in tmp_array must point to:
// - a buffered object; or
// - a source object that has been archived; or
// - (only when dumping dynamic archive) an object in the static archive.
template <typename T>
Array<T>* ArchiveUtils::archive_ptr_array(GrowableArray<T>* tmp_array) {
ArchiveBuilder* builder = ArchiveBuilder::current();
const bool is_dynamic_dump = CDSConfig::is_dumping_dynamic_archive();
Array<T>* archived_array = ArchiveBuilder::new_ro_array<T>(tmp_array->length());
for (int i = 0; i < tmp_array->length(); i++) {
T ptr = tmp_array->at(i);
if (!builder->is_in_buffer_space(ptr)) {
if (is_dynamic_dump && MetaspaceShared::is_in_shared_metaspace(ptr)) {
// We have a pointer that lives in the dynamic archive but points into
// the static archive.
} else {
ptr = builder->get_buffered_addr(ptr);
}
}
archived_array->at_put(i, ptr);
ArchivePtrMarker::mark_pointer(archived_array->adr_at(i));
}
}
return archived_array;

View File

@ -40,6 +40,8 @@
#include "utilities/formatBuffer.hpp"
bool CDSConfig::_is_dumping_static_archive = false;
bool CDSConfig::_is_dumping_preimage_static_archive = false;
bool CDSConfig::_is_dumping_final_static_archive = false;
bool CDSConfig::_is_dumping_dynamic_archive = false;
bool CDSConfig::_is_using_optimized_module_handling = true;
bool CDSConfig::_is_dumping_full_module_graph = true;
@ -47,6 +49,7 @@ bool CDSConfig::_is_using_full_module_graph = true;
bool CDSConfig::_has_aot_linked_classes = false;
bool CDSConfig::_has_archived_invokedynamic = false;
bool CDSConfig::_old_cds_flags_used = false;
bool CDSConfig::_new_aot_flags_used = false;
bool CDSConfig::_disable_heap_dumping = false;
char* CDSConfig::_default_archive_path = nullptr;
@ -64,7 +67,7 @@ int CDSConfig::get_status() {
}
void CDSConfig::initialize() {
if (is_dumping_static_archive()) {
if (is_dumping_static_archive() && !is_dumping_final_static_archive()) {
if (RequireSharedSpaces) {
warning("Cannot dump shared archive while using shared archive");
}
@ -210,6 +213,7 @@ void CDSConfig::init_shared_archive_paths() {
warning("-XX:+AutoCreateSharedArchive is unsupported when base CDS archive is not loaded. Run with -Xlog:cds for more info.");
AutoCreateSharedArchive = false;
}
log_error(cds)("Not a valid %s (%s)", new_aot_flags_used() ? "AOT cache" : "archive", SharedArchiveFile);
Arguments::no_shared_spaces("invalid archive");
}
} else if (base_archive_path == nullptr) {
@ -333,7 +337,11 @@ bool CDSConfig::has_unsupported_runtime_module_options() {
if (RequireSharedSpaces) {
warning("CDS is disabled when the %s option is specified.", option);
} else {
log_info(cds)("CDS is disabled when the %s option is specified.", option);
if (new_aot_flags_used()) {
log_warning(cds)("AOT cache is disabled when the %s option is specified.", option);
} else {
log_info(cds)("CDS is disabled when the %s option is specified.", option);
}
}
return true;
}
@ -343,7 +351,7 @@ bool CDSConfig::has_unsupported_runtime_module_options() {
#define CHECK_ALIAS(f) check_flag_alias(FLAG_IS_DEFAULT(f), #f)
void CDSConfig::check_flag_alias(bool alias_is_default, const char* alias_name) {
if (_old_cds_flags_used && !alias_is_default) {
if (old_cds_flags_used() && !alias_is_default) {
vm_exit_during_initialization(err_msg("Option %s cannot be used at the same time with "
"-Xshare:on, -Xshare:auto, -Xshare:off, -Xshare:dump, "
"DumpLoadedClassList, SharedClassListFile, or SharedArchiveFile",
@ -351,7 +359,7 @@ void CDSConfig::check_flag_alias(bool alias_is_default, const char* alias_name)
}
}
void CDSConfig::check_flag_aliases() {
void CDSConfig::check_aot_flags() {
if (!FLAG_IS_DEFAULT(DumpLoadedClassList) ||
!FLAG_IS_DEFAULT(SharedClassListFile) ||
!FLAG_IS_DEFAULT(SharedArchiveFile)) {
@ -363,30 +371,16 @@ void CDSConfig::check_flag_aliases() {
CHECK_ALIAS(AOTMode);
if (FLAG_IS_DEFAULT(AOTCache) && FLAG_IS_DEFAULT(AOTConfiguration) && FLAG_IS_DEFAULT(AOTMode)) {
// Aliases not used.
// AOTCache/AOTConfiguration/AOTMode not used.
return;
} else {
_new_aot_flags_used = true;
}
if (FLAG_IS_DEFAULT(AOTMode) || strcmp(AOTMode, "auto") == 0 || strcmp(AOTMode, "on") == 0) {
if (!FLAG_IS_DEFAULT(AOTConfiguration)) {
vm_exit_during_initialization("AOTConfiguration can only be used with -XX:AOTMode=record or -XX:AOTMode=create");
}
if (!FLAG_IS_DEFAULT(AOTCache)) {
assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked");
FLAG_SET_ERGO(SharedArchiveFile, AOTCache);
}
UseSharedSpaces = true;
if (FLAG_IS_DEFAULT(AOTMode) || (strcmp(AOTMode, "auto") == 0)) {
RequireSharedSpaces = false;
} else {
assert(strcmp(AOTMode, "on") == 0, "already checked");
RequireSharedSpaces = true;
}
check_aotmode_auto_or_on();
} else if (strcmp(AOTMode, "off") == 0) {
UseSharedSpaces = false;
RequireSharedSpaces = false;
check_aotmode_off();
} else {
// AOTMode is record or create
if (FLAG_IS_DEFAULT(AOTConfiguration)) {
@ -394,32 +388,78 @@ void CDSConfig::check_flag_aliases() {
}
if (strcmp(AOTMode, "record") == 0) {
if (!FLAG_IS_DEFAULT(AOTCache)) {
vm_exit_during_initialization("AOTCache must not be specified when using -XX:AOTMode=record");
}
assert(FLAG_IS_DEFAULT(DumpLoadedClassList), "already checked");
FLAG_SET_ERGO(DumpLoadedClassList, AOTConfiguration);
UseSharedSpaces = false;
RequireSharedSpaces = false;
check_aotmode_record();
} else {
assert(strcmp(AOTMode, "create") == 0, "checked by AOTModeConstraintFunc");
if (FLAG_IS_DEFAULT(AOTCache)) {
vm_exit_during_initialization("AOTCache must be specified when using -XX:AOTMode=create");
}
assert(FLAG_IS_DEFAULT(SharedClassListFile), "already checked");
FLAG_SET_ERGO(SharedClassListFile, AOTConfiguration);
assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked");
FLAG_SET_ERGO(SharedArchiveFile, AOTCache);
CDSConfig::enable_dumping_static_archive();
check_aotmode_create();
}
}
}
void CDSConfig::check_aotmode_off() {
UseSharedSpaces = false;
RequireSharedSpaces = false;
}
void CDSConfig::check_aotmode_auto_or_on() {
if (!FLAG_IS_DEFAULT(AOTConfiguration)) {
vm_exit_during_initialization("AOTConfiguration can only be used with -XX:AOTMode=record or -XX:AOTMode=create");
}
if (!FLAG_IS_DEFAULT(AOTCache)) {
assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked");
FLAG_SET_ERGO(SharedArchiveFile, AOTCache);
}
UseSharedSpaces = true;
if (FLAG_IS_DEFAULT(AOTMode) || (strcmp(AOTMode, "auto") == 0)) {
RequireSharedSpaces = false;
} else {
assert(strcmp(AOTMode, "on") == 0, "already checked");
RequireSharedSpaces = true;
}
}
void CDSConfig::check_aotmode_record() {
if (!FLAG_IS_DEFAULT(AOTCache)) {
vm_exit_during_initialization("AOTCache must not be specified when using -XX:AOTMode=record");
}
assert(FLAG_IS_DEFAULT(DumpLoadedClassList), "already checked");
assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked");
FLAG_SET_ERGO(SharedArchiveFile, AOTConfiguration);
FLAG_SET_ERGO(DumpLoadedClassList, nullptr);
UseSharedSpaces = false;
RequireSharedSpaces = false;
_is_dumping_static_archive = true;
_is_dumping_preimage_static_archive = true;
// At VM exit, the module graph may be contaminated with program states.
// We will rebuild the module graph when dumping the CDS final image.
disable_heap_dumping();
}
void CDSConfig::check_aotmode_create() {
if (FLAG_IS_DEFAULT(AOTCache)) {
vm_exit_during_initialization("AOTCache must be specified when using -XX:AOTMode=create");
}
assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked");
_is_dumping_final_static_archive = true;
FLAG_SET_ERGO(SharedArchiveFile, AOTConfiguration);
UseSharedSpaces = true;
RequireSharedSpaces = true;
if (!FileMapInfo::is_preimage_static_archive(AOTConfiguration)) {
vm_exit_during_initialization("Must be a valid AOT configuration generated by the current JVM", AOTConfiguration);
}
CDSConfig::enable_dumping_static_archive();
}
bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_flag_cmd_line) {
check_flag_aliases();
check_aot_flags();
if (!FLAG_IS_DEFAULT(AOTMode)) {
// Using any form of the new AOTMode switch enables enhanced optimizations.
@ -435,7 +475,9 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla
}
if (is_dumping_static_archive()) {
if (!mode_flag_cmd_line) {
if (is_dumping_preimage_static_archive()) {
// Don't tweak execution mode
} else if (!mode_flag_cmd_line) {
// By default, -Xshare:dump runs in interpreter-only mode, which is required for deterministic archive.
//
// If your classlist is large and you don't care about deterministic dumping, you can use
@ -499,6 +541,20 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla
return true;
}
bool CDSConfig::is_dumping_classic_static_archive() {
return _is_dumping_static_archive &&
!is_dumping_preimage_static_archive() &&
!is_dumping_final_static_archive();
}
bool CDSConfig::is_dumping_preimage_static_archive() {
return _is_dumping_preimage_static_archive;
}
bool CDSConfig::is_dumping_final_static_archive() {
return _is_dumping_final_static_archive;
}
bool CDSConfig::allow_only_single_java_thread() {
// See comments in JVM_StartThread()
return is_dumping_static_archive();
@ -534,6 +590,26 @@ bool CDSConfig::current_thread_is_vm_or_dumper() {
return t != nullptr && (t->is_VM_thread() || t == _dumper_thread);
}
const char* CDSConfig::type_of_archive_being_loaded() {
if (is_dumping_final_static_archive()) {
return "AOT configuration file";
} else if (new_aot_flags_used()) {
return "AOT cache";
} else {
return "shared archive file";
}
}
const char* CDSConfig::type_of_archive_being_written() {
if (is_dumping_preimage_static_archive()) {
return "AOT configuration file";
} else if (new_aot_flags_used()) {
return "AOT cache";
} else {
return "shared archive file";
}
}
// If an incompatible VM options is found, return a text message that explains why
static const char* check_options_incompatible_with_dumping_heap() {
#if INCLUDE_CDS_JAVA_HEAP
@ -574,12 +650,11 @@ bool CDSConfig::are_vm_options_incompatible_with_dumping_heap() {
bool CDSConfig::is_dumping_heap() {
if (!is_dumping_static_archive() // heap dump is not supported in dynamic dump
if (!(is_dumping_classic_static_archive() || is_dumping_final_static_archive())
|| are_vm_options_incompatible_with_dumping_heap()
|| _disable_heap_dumping) {
return false;
}
return true;
}
@ -627,7 +702,9 @@ void CDSConfig::stop_using_full_module_graph(const char* reason) {
}
bool CDSConfig::is_dumping_aot_linked_classes() {
if (is_dumping_dynamic_archive()) {
if (is_dumping_preimage_static_archive()) {
return false;
} else if (is_dumping_dynamic_archive()) {
return is_using_full_module_graph() && AOTClassLinking;
} else if (is_dumping_static_archive()) {
return is_dumping_full_module_graph() && AOTClassLinking;

View File

@ -34,6 +34,8 @@ class JavaThread;
class CDSConfig : public AllStatic {
#if INCLUDE_CDS
static bool _is_dumping_static_archive;
static bool _is_dumping_preimage_static_archive;
static bool _is_dumping_final_static_archive;
static bool _is_dumping_dynamic_archive;
static bool _is_using_optimized_module_handling;
static bool _is_dumping_full_module_graph;
@ -46,6 +48,7 @@ class CDSConfig : public AllStatic {
static char* _dynamic_archive_path;
static bool _old_cds_flags_used;
static bool _new_aot_flags_used;
static bool _disable_heap_dumping;
static JavaThread* _dumper_thread;
@ -57,7 +60,11 @@ class CDSConfig : public AllStatic {
static void init_shared_archive_paths();
static void check_flag_alias(bool alias_is_default, const char* alias_name);
static void check_flag_aliases();
static void check_aot_flags();
static void check_aotmode_off();
static void check_aotmode_auto_or_on();
static void check_aotmode_record();
static void check_aotmode_create();
public:
// Used by jdk.internal.misc.CDS.getCDSConfigStatus();
@ -71,11 +78,14 @@ public:
static void initialize() NOT_CDS_RETURN;
static void set_old_cds_flags_used() { CDS_ONLY(_old_cds_flags_used = true); }
static bool old_cds_flags_used() { return CDS_ONLY(_old_cds_flags_used) NOT_CDS(false); }
static bool new_aot_flags_used() { return CDS_ONLY(_new_aot_flags_used) NOT_CDS(false); }
static void check_internal_module_property(const char* key, const char* value) NOT_CDS_RETURN;
static void check_incompatible_property(const char* key, const char* value) NOT_CDS_RETURN;
static void check_unsupported_dumping_module_options() NOT_CDS_RETURN;
static bool has_unsupported_runtime_module_options() NOT_CDS_RETURN_(false);
static bool check_vm_args_consistency(bool patch_mod_javabase, bool mode_flag_cmd_line) NOT_CDS_RETURN_(true);
static const char* type_of_archive_being_loaded();
static const char* type_of_archive_being_written();
// --- Basic CDS features
@ -88,6 +98,30 @@ public:
static bool is_dumping_static_archive() { return CDS_ONLY(_is_dumping_static_archive) NOT_CDS(false); }
static void enable_dumping_static_archive() { CDS_ONLY(_is_dumping_static_archive = true); }
// A static CDS archive can be dumped in three modes:
//
// "classic" - This is the traditional CDS workflow of
// "java -Xshare:dump -XX:SharedClassListFile=file.txt".
//
// "preimage" - This happens when we execute the JEP 483 training run, e.g:
// "java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconfig -cp app.jar App"
// The above command writes app.aotconfig as a "CDS preimage". This
// is a binary file that contains all the classes loaded during the
// training run, plus profiling data (e.g., the resolved constant pool entries).
//
// "final" - This happens when we execute the JEP 483 assembly phase, e.g:
// "java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconfig -XX:AOTCache=app.aot -cp app.jar"
// The above command loads all classes from app.aotconfig, perform additional linking,
// and writes app.aot as a "CDS final image" file.
//
// The main structural difference between "preimage" and "final" is that the preimage
// - has a different magic number (0xcafea07c)
// - does not have any archived Java heap objects
// - does not have aot-linked classes
static bool is_dumping_classic_static_archive() NOT_CDS_RETURN_(false);
static bool is_dumping_preimage_static_archive() NOT_CDS_RETURN_(false);
static bool is_dumping_final_static_archive() NOT_CDS_RETURN_(false);
// dynamic_archive
static bool is_dumping_dynamic_archive() { return CDS_ONLY(_is_dumping_dynamic_archive) NOT_CDS(false); }
static void enable_dumping_dynamic_archive() { CDS_ONLY(_is_dumping_dynamic_archive = true); }
@ -135,7 +169,6 @@ public:
static void stop_dumping_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN;
static void stop_using_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN;
// Some CDS functions assume that they are called only within a single-threaded context. I.e.,
// they are called from:
// - The VM thread (e.g., inside VM_PopulateDumpSharedSpace)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, 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
@ -105,7 +105,9 @@
constraint(AOTModeConstraintFunc, AtParse) \
\
product(ccstr, AOTConfiguration, nullptr, \
"Configuration information used by CreateAOTCache") \
"The configuration file written by -XX:AOTMode=record, and " \
"loaded by -XX:AOTMode=create. This file contains profiling data "\
"for deciding what contents should be added to AOTCache. ") \
\
product(ccstr, AOTCache, nullptr, \
"Cache for improving start up and warm up") \

View File

@ -189,12 +189,22 @@ enum ClonedVtableKind {
_num_cloned_vtable_kinds
};
// This is a map of all the original vtptrs. E.g., for
// _orig_cpp_vtptrs and _archived_cpp_vtptrs are used for type checking in
// CppVtables::get_archived_vtable().
//
// _orig_cpp_vtptrs is a map of all the original vtptrs. E.g., for
// ConstantPool *cp = new (...) ConstantPool(...) ; // a dynamically allocated constant pool
// the following holds true:
// _orig_cpp_vtptrs[ConstantPool_Kind] == ((intptr_t**)cp)[0]
static intptr_t* _orig_cpp_vtptrs[_num_cloned_vtable_kinds];
// _orig_cpp_vtptrs[ConstantPool_Kind] == ((intptr_t**)cp)[0]
//
// _archived_cpp_vtptrs is a map of all the vptprs used by classes in a preimage. E.g., for
// InstanceKlass* k = a class loaded from the preimage;
// ConstantPool* cp = k->constants();
// the following holds true:
// _archived_cpp_vtptrs[ConstantPool_Kind] == ((intptr_t**)cp)[0]
static bool _orig_cpp_vtptrs_inited = false;
static intptr_t* _orig_cpp_vtptrs[_num_cloned_vtable_kinds];
static intptr_t* _archived_cpp_vtptrs[_num_cloned_vtable_kinds];
template <class T>
void CppVtableCloner<T>::init_orig_cpp_vtptr(int kind) {
@ -212,15 +222,27 @@ void CppVtableCloner<T>::init_orig_cpp_vtptr(int kind) {
// _index[InstanceKlass_Kind]->cloned_vtable() == ((intptr_t**)ik)[0]
static CppVtableInfo* _index[_num_cloned_vtable_kinds];
// Vtables are all fixed offsets from ArchiveBuilder::current()->mapped_base()
// E.g. ConstantPool is at offset 0x58. We can archive these offsets in the
// RO region and use them to alculate their location at runtime without storing
// the pointers in the RW region
// This marks the location in the archive where _index[0] is stored. This location
// will be stored as FileMapHeader::_cloned_vtables_offset into the archive header.
// Serviceability Agent uses this information to determine the vtables of
// archived Metadata objects.
char* CppVtables::_vtables_serialized_base = nullptr;
void CppVtables::dumptime_init(ArchiveBuilder* builder) {
assert(CDSConfig::is_dumping_static_archive(), "cpp tables are only dumped into static archive");
if (CDSConfig::is_dumping_final_static_archive()) {
// When dumping final archive, _index[kind] at this point is in the preimage.
// Remember these vtable pointers in _archived_cpp_vtptrs, as _index[kind] will now be rewritten
// to point to the runtime vtable data.
for (int i = 0; i < _num_cloned_vtable_kinds; i++) {
assert(_index[i] != nullptr, "must have been restored by CppVtables::serialize()");
_archived_cpp_vtptrs[i] = _index[i]->cloned_vtable();
}
} else {
memset(_archived_cpp_vtptrs, 0, sizeof(_archived_cpp_vtptrs));
}
CPP_VTABLE_TYPES_DO(ALLOCATE_AND_INITIALIZE_VTABLE);
size_t cpp_tables_size = builder->rw_region()->top() - builder->rw_region()->base();
@ -267,7 +289,8 @@ intptr_t* CppVtables::get_archived_vtable(MetaspaceObj::Type msotype, address ob
break;
default:
for (kind = 0; kind < _num_cloned_vtable_kinds; kind ++) {
if (vtable_of((Metadata*)obj) == _orig_cpp_vtptrs[kind]) {
if (vtable_of((Metadata*)obj) == _orig_cpp_vtptrs[kind] ||
vtable_of((Metadata*)obj) == _archived_cpp_vtptrs[kind]) {
break;
}
}
@ -295,5 +318,6 @@ void CppVtables::zero_archived_vtables() {
bool CppVtables::is_valid_shared_method(const Method* m) {
assert(MetaspaceShared::is_in_shared_metaspace(m), "must be");
return vtable_of(m) == _index[Method_Kind]->cloned_vtable();
return vtable_of(m) == _index[Method_Kind]->cloned_vtable() ||
vtable_of(m) == _archived_cpp_vtptrs[Method_Kind];
}

View File

@ -66,9 +66,9 @@ void DumpTimeClassInfo::add_verification_constraint(InstanceKlass* k, Symbol* na
GrowableArray<char>* vcflags_array = _verifier_constraint_flags;
char c = 0;
c |= from_field_is_protected ? SystemDictionaryShared::FROM_FIELD_IS_PROTECTED : 0;
c |= from_is_array ? SystemDictionaryShared::FROM_IS_ARRAY : 0;
c |= from_is_object ? SystemDictionaryShared::FROM_IS_OBJECT : 0;
c |= from_field_is_protected ? RunTimeClassInfo::FROM_FIELD_IS_PROTECTED : 0;
c |= from_is_array ? RunTimeClassInfo::FROM_IS_ARRAY : 0;
c |= from_is_object ? RunTimeClassInfo::FROM_IS_OBJECT : 0;
vcflags_array->append(c);
if (log_is_enabled(Trace, cds, verification)) {
@ -142,7 +142,7 @@ bool DumpTimeClassInfo::is_builtin() {
}
DumpTimeClassInfo* DumpTimeSharedClassTable::allocate_info(InstanceKlass* k) {
assert(!k->is_shared(), "Do not call with shared classes");
assert(CDSConfig::is_dumping_final_static_archive() || !k->is_shared(), "Do not call with shared classes");
bool created;
DumpTimeClassInfo* p = put_if_absent(k, &created);
assert(created, "must not exist in table");
@ -151,7 +151,7 @@ DumpTimeClassInfo* DumpTimeSharedClassTable::allocate_info(InstanceKlass* k) {
}
DumpTimeClassInfo* DumpTimeSharedClassTable::get_info(InstanceKlass* k) {
assert(!k->is_shared(), "Do not call with shared classes");
assert(CDSConfig::is_dumping_final_static_archive() || !k->is_shared(), "Do not call with shared classes");
DumpTimeClassInfo* p = get(k);
assert(p != nullptr, "we must not see any non-shared InstanceKlass* that's "
"not stored with SystemDictionaryShared::init_dumptime_info");

View File

@ -388,8 +388,9 @@ public:
void doit() {
ResourceMark rm;
if (AllowArchivingWithJavaAgent) {
log_warning(cds)("This archive was created with AllowArchivingWithJavaAgent. It should be used "
"for testing purposes only and should not be used in a production environment");
log_warning(cds)("This %s was created with AllowArchivingWithJavaAgent. It should be used "
"for testing purposes only and should not be used in a production environment",
CDSConfig::type_of_archive_being_loaded());
}
AOTClassLocationConfig::dumptime_check_nonempty_dirs();
_builder.doit();

View File

@ -151,6 +151,13 @@ FileMapInfo::~FileMapInfo() {
}
}
void FileMapInfo::free_current_info() {
assert(CDSConfig::is_dumping_final_static_archive(), "only supported in this mode");
assert(_current_info != nullptr, "sanity");
delete _current_info;
assert(_current_info == nullptr, "sanity"); // Side effect expected from the above "delete" operator.
}
void FileMapInfo::populate_header(size_t core_region_alignment) {
assert(_header == nullptr, "Sanity check");
size_t c_header_size;
@ -191,7 +198,13 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment,
set_header_size((unsigned int)header_size);
set_base_archive_name_offset((unsigned int)base_archive_name_offset);
set_base_archive_name_size((unsigned int)base_archive_name_size);
set_magic(CDSConfig::is_dumping_dynamic_archive() ? CDS_DYNAMIC_ARCHIVE_MAGIC : CDS_ARCHIVE_MAGIC);
if (CDSConfig::is_dumping_dynamic_archive()) {
set_magic(CDS_DYNAMIC_ARCHIVE_MAGIC);
} else if (CDSConfig::is_dumping_preimage_static_archive()) {
set_magic(CDS_PREIMAGE_ARCHIVE_MAGIC);
} else {
set_magic(CDS_ARCHIVE_MAGIC);
}
set_version(CURRENT_CDS_ARCHIVE_VERSION);
if (!info->is_static() && base_archive_name_size != 0) {
@ -386,7 +399,7 @@ public:
assert(_archive_name != nullptr, "Archive name is null");
_fd = os::open(_archive_name, O_RDONLY | O_BINARY, 0);
if (_fd < 0) {
log_info(cds)("Specified shared archive not found (%s)", _archive_name);
log_info(cds)("Specified %s not found (%s)", CDSConfig::type_of_archive_being_loaded(), _archive_name);
return false;
}
return initialize(_fd);
@ -397,30 +410,32 @@ public:
assert(_archive_name != nullptr, "Archive name is null");
assert(fd != -1, "Archive must be opened already");
// First read the generic header so we know the exact size of the actual header.
const char* file_type = CDSConfig::type_of_archive_being_loaded();
GenericCDSFileMapHeader gen_header;
size_t size = sizeof(GenericCDSFileMapHeader);
os::lseek(fd, 0, SEEK_SET);
size_t n = ::read(fd, (void*)&gen_header, (unsigned int)size);
if (n != size) {
log_warning(cds)("Unable to read generic CDS file map header from shared archive");
log_warning(cds)("Unable to read generic CDS file map header from %s", file_type);
return false;
}
if (gen_header._magic != CDS_ARCHIVE_MAGIC &&
gen_header._magic != CDS_DYNAMIC_ARCHIVE_MAGIC) {
log_warning(cds)("The shared archive file has a bad magic number: %#x", gen_header._magic);
gen_header._magic != CDS_DYNAMIC_ARCHIVE_MAGIC &&
gen_header._magic != CDS_PREIMAGE_ARCHIVE_MAGIC) {
log_warning(cds)("The %s has a bad magic number: %#x", file_type, gen_header._magic);
return false;
}
if (gen_header._version < CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION) {
log_warning(cds)("Cannot handle shared archive file version 0x%x. Must be at least 0x%x.",
gen_header._version, CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION);
log_warning(cds)("Cannot handle %s version 0x%x. Must be at least 0x%x.",
file_type, gen_header._version, CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION);
return false;
}
if (gen_header._version != CURRENT_CDS_ARCHIVE_VERSION) {
log_warning(cds)("The shared archive file version 0x%x does not match the required version 0x%x.",
gen_header._version, CURRENT_CDS_ARCHIVE_VERSION);
log_warning(cds)("The %s version 0x%x does not match the required version 0x%x.",
file_type, gen_header._version, CURRENT_CDS_ARCHIVE_VERSION);
}
size_t filelen = os::lseek(fd, 0, SEEK_END);
@ -435,7 +450,7 @@ public:
os::lseek(fd, 0, SEEK_SET);
n = ::read(fd, (void*)_header, (unsigned int)size);
if (n != size) {
log_warning(cds)("Unable to read actual CDS file map header from shared archive");
log_warning(cds)("Unable to read file map header from %s", file_type);
return false;
}
@ -462,6 +477,18 @@ public:
return _base_archive_name;
}
bool is_static_archive() const {
return _header->_magic == CDS_ARCHIVE_MAGIC;
}
bool is_dynamic_archive() const {
return _header->_magic == CDS_DYNAMIC_ARCHIVE_MAGIC;
}
bool is_preimage_static_archive() const {
return _header->_magic == CDS_PREIMAGE_ARCHIVE_MAGIC;
}
private:
bool check_header_crc() const {
if (VerifySharedSpaces) {
@ -487,7 +514,8 @@ public:
name_offset, name_size);
return false;
}
if (_header->_magic == CDS_ARCHIVE_MAGIC) {
if (is_static_archive() || is_preimage_static_archive()) {
if (name_offset != 0) {
log_warning(cds)("static shared archive must have zero _base_archive_name_offset");
return false;
@ -497,7 +525,7 @@ public:
return false;
}
} else {
assert(_header->_magic == CDS_DYNAMIC_ARCHIVE_MAGIC, "must be");
assert(is_dynamic_archive(), "must be");
if ((name_size == 0 && name_offset != 0) ||
(name_size != 0 && name_offset == 0)) {
// If either is zero, both must be zero. This indicates that we are using the default base archive.
@ -545,7 +573,12 @@ bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name,
return false;
}
GenericCDSFileMapHeader* header = file_helper.get_generic_file_header();
if (header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) {
switch (header->_magic) {
case CDS_PREIMAGE_ARCHIVE_MAGIC:
return false; // This is a binary config file, not a proper archive
case CDS_DYNAMIC_ARCHIVE_MAGIC:
break;
default:
assert(header->_magic == CDS_ARCHIVE_MAGIC, "must be");
if (AutoCreateSharedArchive) {
log_warning(cds)("AutoCreateSharedArchive is ignored because %s is a static archive", archive_name);
@ -563,6 +596,14 @@ bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name,
return true;
}
bool FileMapInfo::is_preimage_static_archive(const char* file) {
FileHeaderHelper file_helper(file, false);
if (!file_helper.initialize()) {
return false;
}
return file_helper.is_preimage_static_archive();
}
// Read the FileMapInfo information from the file.
bool FileMapInfo::init_from_file(int fd) {
@ -573,9 +614,17 @@ bool FileMapInfo::init_from_file(int fd) {
}
GenericCDSFileMapHeader* gen_header = file_helper.get_generic_file_header();
const char* file_type = CDSConfig::type_of_archive_being_loaded();
if (_is_static) {
if (gen_header->_magic != CDS_ARCHIVE_MAGIC) {
log_warning(cds)("Not a base shared archive: %s", _full_path);
if ((gen_header->_magic == CDS_ARCHIVE_MAGIC) ||
(gen_header->_magic == CDS_PREIMAGE_ARCHIVE_MAGIC && CDSConfig::is_dumping_final_static_archive())) {
// Good
} else {
if (CDSConfig::new_aot_flags_used()) {
log_warning(cds)("Not a valid %s %s", file_type, _full_path);
} else {
log_warning(cds)("Not a base shared archive: %s", _full_path);
}
return false;
}
} else {
@ -597,7 +646,7 @@ bool FileMapInfo::init_from_file(int fd) {
if (header()->version() != CURRENT_CDS_ARCHIVE_VERSION) {
log_info(cds)("_version expected: 0x%x", CURRENT_CDS_ARCHIVE_VERSION);
log_info(cds)(" actual: 0x%x", header()->version());
log_warning(cds)("The shared archive file has the wrong version.");
log_warning(cds)("The %s has the wrong version.", file_type);
return false;
}
@ -609,7 +658,7 @@ bool FileMapInfo::init_from_file(int fd) {
log_info(cds)("_header_size: " UINT32_FORMAT, header_size);
log_info(cds)("base_archive_name_size: " UINT32_FORMAT, header()->base_archive_name_size());
log_info(cds)("base_archive_name_offset: " UINT32_FORMAT, header()->base_archive_name_offset());
log_warning(cds)("The shared archive file has an incorrect header size.");
log_warning(cds)("The %s has an incorrect header size.", file_type);
return false;
}
}
@ -626,8 +675,8 @@ bool FileMapInfo::init_from_file(int fd) {
if (strncmp(actual_ident, expected_ident, JVM_IDENT_MAX-1) != 0) {
log_info(cds)("_jvm_ident expected: %s", expected_ident);
log_info(cds)(" actual: %s", actual_ident);
log_warning(cds)("The shared archive file was created by a different"
" version or build of HotSpot");
log_warning(cds)("The %s was created by a different"
" version or build of HotSpot", file_type);
return false;
}
@ -638,7 +687,7 @@ bool FileMapInfo::init_from_file(int fd) {
for (int i = 0; i < MetaspaceShared::n_regions; i++) {
FileMapRegion* r = region_at(i);
if (r->file_offset() > len || len - r->file_offset() < r->used()) {
log_warning(cds)("The shared archive file has been truncated.");
log_warning(cds)("The %s has been truncated.", file_type);
return false;
}
}
@ -658,18 +707,21 @@ bool FileMapInfo::open_for_read() {
if (_file_open) {
return true;
}
log_info(cds)("trying to map %s", _full_path);
const char* file_type = CDSConfig::type_of_archive_being_loaded();
const char* info = CDSConfig::is_dumping_final_static_archive() ?
"AOTConfiguration file " : "";
log_info(cds)("trying to map %s%s", info, _full_path);
int fd = os::open(_full_path, O_RDONLY | O_BINARY, 0);
if (fd < 0) {
if (errno == ENOENT) {
log_info(cds)("Specified shared archive not found (%s)", _full_path);
log_info(cds)("Specified %s not found (%s)", file_type, _full_path);
} else {
log_warning(cds)("Failed to open shared archive file (%s)",
log_warning(cds)("Failed to open %s (%s)", file_type,
os::strerror(errno));
}
return false;
} else {
log_info(cds)("Opened archive %s.", _full_path);
log_info(cds)("Opened %s %s.", file_type, _full_path);
}
_fd = fd;
@ -682,20 +734,25 @@ bool FileMapInfo::open_for_read() {
void FileMapInfo::open_for_write() {
LogMessage(cds) msg;
if (msg.is_info()) {
msg.info("Dumping shared data to file: ");
if (CDSConfig::is_dumping_preimage_static_archive()) {
msg.info("Writing binary AOTConfiguration file: ");
} else {
msg.info("Dumping shared data to file: ");
}
msg.info(" %s", _full_path);
}
#ifdef _WINDOWS // On Windows, need WRITE permission to remove the file.
chmod(_full_path, _S_IREAD | _S_IWRITE);
chmod(_full_path, _S_IREAD | _S_IWRITE);
#endif
// Use remove() to delete the existing file because, on Unix, this will
// allow processes that have it open continued access to the file.
remove(_full_path);
int fd = os::open(_full_path, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0444);
int mode = CDSConfig::is_dumping_preimage_static_archive() ? 0666 : 0444;
int fd = os::open(_full_path, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, mode);
if (fd < 0) {
log_error(cds)("Unable to create shared archive file %s: (%s).", _full_path,
log_error(cds)("Unable to create %s %s: (%s).", CDSConfig::type_of_archive_being_written(), _full_path,
os::strerror(errno));
MetaspaceShared::writing_error();
return;
@ -951,7 +1008,14 @@ void FileMapInfo::write_bytes(const void* buffer, size_t nbytes) {
// If the shared archive is corrupted, close it and remove it.
close();
remove(_full_path);
MetaspaceShared::writing_error("Unable to write to shared archive file.");
if (CDSConfig::is_dumping_preimage_static_archive()) {
MetaspaceShared::writing_error("Unable to write to AOT configuration file.");
} else if (CDSConfig::new_aot_flags_used()) {
MetaspaceShared::writing_error("Unable to write to AOT cache.");
} else {
MetaspaceShared::writing_error("Unable to write to shared archive.");
}
}
_file_offset += nbytes;
}
@ -1793,15 +1857,16 @@ int FileMapHeader::compute_crc() {
// This function should only be called during run time with UseSharedSpaces enabled.
bool FileMapHeader::validate() {
const char* file_type = CDSConfig::type_of_archive_being_loaded();
if (_obj_alignment != ObjectAlignmentInBytes) {
log_info(cds)("The shared archive file's ObjectAlignmentInBytes of %d"
log_info(cds)("The %s's ObjectAlignmentInBytes of %d"
" does not equal the current ObjectAlignmentInBytes of %d.",
_obj_alignment, ObjectAlignmentInBytes);
file_type, _obj_alignment, ObjectAlignmentInBytes);
return false;
}
if (_compact_strings != CompactStrings) {
log_info(cds)("The shared archive file's CompactStrings setting (%s)"
" does not equal the current CompactStrings setting (%s).",
log_info(cds)("The %s's CompactStrings setting (%s)"
" does not equal the current CompactStrings setting (%s).", file_type,
_compact_strings ? "enabled" : "disabled",
CompactStrings ? "enabled" : "disabled");
return false;
@ -1825,8 +1890,8 @@ bool FileMapHeader::validate() {
if (!_verify_local && BytecodeVerificationLocal) {
// we cannot load boot classes, so there's no point of using the CDS archive
log_info(cds)("The shared archive file's BytecodeVerificationLocal setting (%s)"
" does not equal the current BytecodeVerificationLocal setting (%s).",
log_info(cds)("The %s's BytecodeVerificationLocal setting (%s)"
" does not equal the current BytecodeVerificationLocal setting (%s).", file_type,
_verify_local ? "enabled" : "disabled",
BytecodeVerificationLocal ? "enabled" : "disabled");
return false;
@ -1837,8 +1902,8 @@ bool FileMapHeader::validate() {
if (_has_platform_or_app_classes
&& !_verify_remote // we didn't verify the archived platform/app classes
&& BytecodeVerificationRemote) { // but we want to verify all loaded platform/app classes
log_info(cds)("The shared archive file was created with less restrictive "
"verification setting than the current setting.");
log_info(cds)("The %s was created with less restrictive "
"verification setting than the current setting.", file_type);
// Pretend that we didn't have any archived platform/app classes, so they won't be loaded
// by SystemDictionaryShared.
_has_platform_or_app_classes = false;
@ -1850,32 +1915,32 @@ bool FileMapHeader::validate() {
// while AllowArchivingWithJavaAgent is set during the current run.
if (_allow_archiving_with_java_agent && !AllowArchivingWithJavaAgent) {
log_warning(cds)("The setting of the AllowArchivingWithJavaAgent is different "
"from the setting in the shared archive.");
"from the setting in the %s.", file_type);
return false;
}
if (_allow_archiving_with_java_agent) {
log_warning(cds)("This archive was created with AllowArchivingWithJavaAgent. It should be used "
"for testing purposes only and should not be used in a production environment");
log_warning(cds)("This %s was created with AllowArchivingWithJavaAgent. It should be used "
"for testing purposes only and should not be used in a production environment", file_type);
}
log_info(cds)("Archive was created with UseCompressedOops = %d, UseCompressedClassPointers = %d, UseCompactObjectHeaders = %d",
compressed_oops(), compressed_class_pointers(), compact_headers());
log_info(cds)("The %s was created with UseCompressedOops = %d, UseCompressedClassPointers = %d, UseCompactObjectHeaders = %d",
file_type, compressed_oops(), compressed_class_pointers(), compact_headers());
if (compressed_oops() != UseCompressedOops || compressed_class_pointers() != UseCompressedClassPointers) {
log_warning(cds)("Unable to use shared archive.\nThe saved state of UseCompressedOops and UseCompressedClassPointers is "
"different from runtime, CDS will be disabled.");
log_warning(cds)("Unable to use %s.\nThe saved state of UseCompressedOops and UseCompressedClassPointers is "
"different from runtime, CDS will be disabled.", file_type);
return false;
}
if (compact_headers() != UseCompactObjectHeaders) {
log_warning(cds)("Unable to use shared archive.\nThe shared archive file's UseCompactObjectHeaders setting (%s)"
" does not equal the current UseCompactObjectHeaders setting (%s).",
log_warning(cds)("Unable to use %s.\nThe %s's UseCompactObjectHeaders setting (%s)"
" does not equal the current UseCompactObjectHeaders setting (%s).", file_type, file_type,
_compact_headers ? "enabled" : "disabled",
UseCompactObjectHeaders ? "enabled" : "disabled");
return false;
}
if (!_use_optimized_module_handling) {
if (!_use_optimized_module_handling && !CDSConfig::is_dumping_final_static_archive()) {
CDSConfig::stop_using_optimized_module_handling();
log_info(cds)("optimized module handling: disabled because archive was created without optimized module handling");
}

View File

@ -270,12 +270,15 @@ public:
FileMapHeader *header() const { return _header; }
static bool get_base_archive_name_from_header(const char* archive_name,
char** base_archive_name);
static bool is_preimage_static_archive(const char* file);
bool init_from_file(int fd);
void log_paths(const char* msg, int start_idx, int end_idx);
FileMapInfo(const char* full_apth, bool is_static);
~FileMapInfo();
static void free_current_info();
// Accessors
int compute_header_crc() const { return header()->compute_crc(); }

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 2024, 2025, 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.
*
*/
#include "cds/aotConstantPoolResolver.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/archiveUtils.inline.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/finalImageRecipes.hpp"
#include "classfile/classLoader.hpp"
#include "classfile/javaClasses.hpp"
#include "classfile/systemDictionary.hpp"
#include "classfile/systemDictionaryShared.hpp"
#include "classfile/vmClasses.hpp"
#include "memory/oopFactory.hpp"
#include "memory/resourceArea.hpp"
#include "oops/constantPool.inline.hpp"
#include "runtime/handles.inline.hpp"
static FinalImageRecipes* _final_image_recipes = nullptr;
void* FinalImageRecipes::operator new(size_t size) throw() {
return ArchiveBuilder::current()->ro_region_alloc(size);
}
void FinalImageRecipes::record_recipes_impl() {
assert(CDSConfig::is_dumping_preimage_static_archive(), "must be");
ResourceMark rm;
GrowableArray<Klass*>* klasses = ArchiveBuilder::current()->klasses();
// Record the indys that have been resolved in the training run. These indys will be
// resolved during the final image assembly.
GrowableArray<InstanceKlass*> tmp_indy_klasses;
GrowableArray<Array<int>*> tmp_indy_cp_indices;
int total_indys_to_resolve = 0;
for (int i = 0; i < klasses->length(); i++) {
Klass* k = klasses->at(i);
if (k->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(k);
GrowableArray<int> indices;
if (ik->constants()->cache() != nullptr) {
Array<ResolvedIndyEntry>* tmp_indy_entries = ik->constants()->cache()->resolved_indy_entries();
if (tmp_indy_entries != nullptr) {
for (int i = 0; i < tmp_indy_entries->length(); i++) {
ResolvedIndyEntry* rie = tmp_indy_entries->adr_at(i);
int cp_index = rie->constant_pool_index();
if (rie->is_resolved()) {
indices.append(cp_index);
}
}
}
}
if (indices.length() > 0) {
tmp_indy_klasses.append(ArchiveBuilder::current()->get_buffered_addr(ik));
tmp_indy_cp_indices.append(ArchiveUtils::archive_array(&indices));
total_indys_to_resolve += indices.length();
}
}
}
_all_klasses = ArchiveUtils::archive_array(klasses);
ArchivePtrMarker::mark_pointer(&_all_klasses);
assert(tmp_indy_klasses.length() == tmp_indy_cp_indices.length(), "must be");
if (tmp_indy_klasses.length() > 0) {
_indy_klasses = ArchiveUtils::archive_array(&tmp_indy_klasses);
_indy_cp_indices = ArchiveUtils::archive_array(&tmp_indy_cp_indices);
ArchivePtrMarker::mark_pointer(&_indy_klasses);
ArchivePtrMarker::mark_pointer(&_indy_cp_indices);
}
log_info(cds)("%d indies in %d classes will be resolved in final CDS image", total_indys_to_resolve, tmp_indy_klasses.length());
}
void FinalImageRecipes::load_all_classes(TRAPS) {
assert(CDSConfig::is_dumping_final_static_archive(), "sanity");
Handle class_loader(THREAD, SystemDictionary::java_system_loader());
for (int i = 0; i < _all_klasses->length(); i++) {
Klass* k = _all_klasses->at(i);
if (k->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(k);
if (!ik->is_shared_unregistered_class() && !ik->is_hidden()) {
Klass* actual = SystemDictionary::resolve_or_fail(ik->name(), class_loader, true, CHECK);
if (actual != ik) {
ResourceMark rm(THREAD);
log_error(cds)("Unable to resolve class from CDS archive: %s", ik->external_name());
log_error(cds)("Expected: " INTPTR_FORMAT ", actual: " INTPTR_FORMAT, p2i(ik), p2i(actual));
log_error(cds)("Please check if your VM command-line is the same as in the training run");
MetaspaceShared::unrecoverable_writing_error();
}
assert(ik->is_loaded(), "must be");
ik->link_class(CHECK);
}
}
}
}
void FinalImageRecipes::apply_recipes_for_invokedynamic(TRAPS) {
assert(CDSConfig::is_dumping_final_static_archive(), "must be");
if (CDSConfig::is_dumping_invokedynamic() && _indy_klasses != nullptr) {
assert(_indy_cp_indices != nullptr, "must be");
for (int i = 0; i < _indy_klasses->length(); i++) {
InstanceKlass* ik = _indy_klasses->at(i);
ConstantPool* cp = ik->constants();
Array<int>* cp_indices = _indy_cp_indices->at(i);
GrowableArray<bool> preresolve_list(cp->length(), cp->length(), false);
for (int j = 0; j < cp_indices->length(); j++) {
preresolve_list.at_put(cp_indices->at(j), true);
}
AOTConstantPoolResolver::preresolve_indy_cp_entries(THREAD, ik, &preresolve_list);
}
}
}
void FinalImageRecipes::record_recipes() {
_final_image_recipes = new FinalImageRecipes();
_final_image_recipes->record_recipes_impl();
}
void FinalImageRecipes::apply_recipes(TRAPS) {
assert(CDSConfig::is_dumping_final_static_archive(), "must be");
if (_final_image_recipes != nullptr) {
_final_image_recipes->apply_recipes_impl(THREAD);
if (HAS_PENDING_EXCEPTION) {
log_error(cds)("%s: %s", PENDING_EXCEPTION->klass()->external_name(),
java_lang_String::as_utf8_string(java_lang_Throwable::message(PENDING_EXCEPTION)));
log_error(cds)("Please check if your VM command-line is the same as in the training run");
MetaspaceShared::unrecoverable_writing_error("Unexpected exception, use -Xlog:cds,exceptions=trace for detail");
}
}
// Set it to null as we don't need to write this table into the final image.
_final_image_recipes = nullptr;
}
void FinalImageRecipes::apply_recipes_impl(TRAPS) {
load_all_classes(CHECK);
apply_recipes_for_invokedynamic(CHECK);
}
void FinalImageRecipes::serialize(SerializeClosure* soc) {
soc->do_ptr((void**)&_final_image_recipes);
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2024, 2025, 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.
*
*/
#ifndef SHARE_CDS_FINALIMAGERECIPES_HPP
#define SHARE_CDS_FINALIMAGERECIPES_HPP
#include "oops/oopsHierarchy.hpp"
#include "utilities/exceptions.hpp"
class InstanceKlass;
class Klass;
template <typename T> class GrowableArray;
template <typename T> class Array;
// This class is used for transferring information from the AOTConfiguration file (aka the "preimage")
// to the JVM that creates the AOTCache (aka the "final image").
// - The recipes are recorded when CDSConfig::is_dumping_preimage_static_archive() is true.
// - The recipes are applied when CDSConfig::is_dumping_final_static_archive() is true.
// The following information are recorded:
// - The list of all classes that are stored in the AOTConfiguration file.
// - The list of all classes that require AOT resolution of invokedynamic call sites.
class FinalImageRecipes {
// A list of all the archived classes from the preimage. We want to transfer all of these
// into the final image.
Array<Klass*>* _all_klasses;
// The classes who have resolved at least one indy CP entry during the training run.
// _indy_cp_indices[i] is a list of all resolved CP entries for _indy_klasses[i].
Array<InstanceKlass*>* _indy_klasses;
Array<Array<int>*>* _indy_cp_indices;
FinalImageRecipes() : _indy_klasses(nullptr), _indy_cp_indices(nullptr) {}
void* operator new(size_t size) throw();
// Called when dumping preimage
void record_recipes_impl();
// Called when dumping final image
void apply_recipes_impl(TRAPS);
void load_all_classes(TRAPS);
void apply_recipes_for_invokedynamic(TRAPS);
public:
static void serialize(SerializeClosure* soc);
// Called when dumping preimage
static void record_recipes();
// Called when dumping final image
static void apply_recipes(TRAPS);
};
#endif // SHARE_CDS_FINALIMAGERECIPES_HPP

View File

@ -401,6 +401,11 @@ objArrayOop HeapShared::scratch_resolved_references(ConstantPool* src) {
return (objArrayOop)_scratch_references_table->get_oop(src);
}
void HeapShared::init_dumping() {
_scratch_java_mirror_table = new (mtClass)MetaspaceObjToOopHandleTable();
_scratch_references_table = new (mtClass)MetaspaceObjToOopHandleTable();
}
void HeapShared::init_scratch_objects(TRAPS) {
for (int i = T_BOOLEAN; i < T_VOID+1; i++) {
BasicType bt = (BasicType)i;
@ -409,8 +414,6 @@ void HeapShared::init_scratch_objects(TRAPS) {
_scratch_basic_type_mirrors[i] = OopHandle(Universe::vm_global(), m);
}
}
_scratch_java_mirror_table = new (mtClass)MetaspaceObjToOopHandleTable();
_scratch_references_table = new (mtClass)MetaspaceObjToOopHandleTable();
}
// Given java_mirror that represents a (primitive or reference) type T,

View File

@ -405,6 +405,7 @@ private:
static void write_heap(ArchiveHeapInfo* heap_info) NOT_CDS_JAVA_HEAP_RETURN;
static objArrayOop scratch_resolved_references(ConstantPool* src);
static void add_scratch_resolved_references(ConstantPool* src, objArrayOop dest) NOT_CDS_JAVA_HEAP_RETURN;
static void init_dumping() NOT_CDS_JAVA_HEAP_RETURN;
static void init_scratch_objects(TRAPS) NOT_CDS_JAVA_HEAP_RETURN;
static void init_box_classes(TRAPS) NOT_CDS_JAVA_HEAP_RETURN;
static bool is_heap_region(int idx) {

View File

@ -263,4 +263,8 @@ void LambdaFormInvokers::read_static_archive_invokers() {
void LambdaFormInvokers::serialize(SerializeClosure* soc) {
soc->do_ptr(&_static_archive_invokers);
if (soc->reading() && CDSConfig::is_dumping_final_static_archive()) {
LambdaFormInvokers::read_static_archive_invokers();
_static_archive_invokers = nullptr;
}
}

View File

@ -39,6 +39,7 @@
#include "cds/dumpAllocStats.hpp"
#include "cds/dynamicArchive.hpp"
#include "cds/filemap.hpp"
#include "cds/finalImageRecipes.hpp"
#include "cds/heapShared.hpp"
#include "cds/lambdaFormInvokers.hpp"
#include "cds/metaspaceShared.hpp"
@ -489,6 +490,7 @@ void MetaspaceShared::serialize(SerializeClosure* soc) {
HeapShared::serialize_tables(soc);
SystemDictionaryShared::serialize_dictionary_headers(soc);
AOTLinkedClassBulkLoader::serialize(soc, true);
FinalImageRecipes::serialize(soc);
InstanceMirrorKlass::serialize_offsets(soc);
// Dump/restore well known classes (pointers)
@ -609,6 +611,9 @@ char* VM_PopulateDumpSharedSpace::dump_read_only_tables(AOTClassLocationConfig*&
SystemDictionaryShared::write_to_archive();
cl_config = AOTClassLocationConfig::dumptime()->write_to_archive();
AOTClassLinker::write_to_archive();
if (CDSConfig::is_dumping_preimage_static_archive()) {
FinalImageRecipes::record_recipes();
}
MetaspaceShared::write_method_handle_intrinsics();
// Write lambform lines into archive
@ -624,7 +629,9 @@ char* VM_PopulateDumpSharedSpace::dump_read_only_tables(AOTClassLocationConfig*&
}
void VM_PopulateDumpSharedSpace::doit() {
guarantee(!CDSConfig::is_using_archive(), "We should not be using an archive when we dump");
if (!CDSConfig::is_dumping_final_static_archive()) {
guarantee(!CDSConfig::is_using_archive(), "We should not be using an archive when we dump");
}
DEBUG_ONLY(SystemDictionaryShared::NoClassLoadingMark nclm);
@ -679,7 +686,13 @@ void VM_PopulateDumpSharedSpace::doit() {
CppVtables::zero_archived_vtables();
// Write the archive file
const char* static_archive = CDSConfig::static_archive_path();
const char* static_archive;
if (CDSConfig::is_dumping_final_static_archive()) {
static_archive = AOTCache;
FileMapInfo::free_current_info();
} else {
static_archive = CDSConfig::static_archive_path();
}
assert(static_archive != nullptr, "SharedArchiveFile not set?");
_map_info = new FileMapInfo(static_archive, true);
_map_info->populate_header(MetaspaceShared::core_region_alignment());
@ -743,7 +756,7 @@ bool MetaspaceShared::link_class_for_cds(InstanceKlass* ik, TRAPS) {
void MetaspaceShared::link_shared_classes(bool jcmd_request, TRAPS) {
AOTClassLinker::initialize();
if (!jcmd_request) {
if (!jcmd_request && !CDSConfig::is_dumping_final_static_archive()) {
LambdaFormInvokers::regenerate_holder_classes(CHECK);
}
@ -778,6 +791,10 @@ void MetaspaceShared::link_shared_classes(bool jcmd_request, TRAPS) {
// Class linking includes verification which may load more classes.
// Keep scanning until we have linked no more classes.
}
if (CDSConfig::is_dumping_final_static_archive()) {
FinalImageRecipes::apply_recipes(CHECK);
}
}
void MetaspaceShared::prepare_for_dumping() {
@ -804,13 +821,18 @@ void MetaspaceShared::preload_and_dump(TRAPS) {
}
}
if (!CDSConfig::old_cds_flags_used()) {
// The JLI launcher only recognizes the "old" -Xshare:dump flag.
// When the new -XX:AOTMode=create flag is used, we can't return
// to the JLI launcher, as the launcher will fail when trying to
// run the main class, which is not what we want.
tty->print_cr("AOTCache creation is complete: %s", AOTCache);
vm_exit(0);
if (CDSConfig::new_aot_flags_used()) {
if (CDSConfig::is_dumping_preimage_static_archive()) {
tty->print_cr("AOTConfiguration recorded: %s", AOTConfiguration);
vm_exit(0);
} else {
// The JLI launcher only recognizes the "old" -Xshare:dump flag.
// When the new -XX:AOTMode=create flag is used, we can't return
// to the JLI launcher, as the launcher will fail when trying to
// run the main class, which is not what we want.
tty->print_cr("AOTCache creation is complete: %s", AOTCache);
vm_exit(0);
}
}
}
@ -910,12 +932,34 @@ void MetaspaceShared::exercise_runtime_cds_code(TRAPS) {
}
void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS) {
preload_classes(CHECK);
if (CDSConfig::is_dumping_classic_static_archive()) {
// We are running with -Xshare:dump
preload_classes(CHECK);
if (SharedArchiveConfigFile) {
log_info(cds)("Reading extra data from %s ...", SharedArchiveConfigFile);
read_extra_data(THREAD, SharedArchiveConfigFile);
log_info(cds)("Reading extra data: done.");
if (SharedArchiveConfigFile) {
log_info(cds)("Reading extra data from %s ...", SharedArchiveConfigFile);
read_extra_data(THREAD, SharedArchiveConfigFile);
log_info(cds)("Reading extra data: done.");
}
}
if (CDSConfig::is_dumping_preimage_static_archive()) {
log_info(cds)("Reading lambda form invokers from JDK default classlist ...");
char default_classlist[JVM_MAXPATHLEN];
get_default_classlist(default_classlist, sizeof(default_classlist));
struct stat statbuf;
if (os::stat(default_classlist, &statbuf) == 0) {
ClassListParser::parse_classlist(default_classlist,
ClassListParser::_parse_lambda_forms_invokers_only, CHECK);
}
}
if (CDSConfig::is_dumping_final_static_archive()) {
if (ExtraSharedClassListFile) {
log_info(cds)("Loading extra classes from %s ...", ExtraSharedClassListFile);
ClassListParser::parse_classlist(ExtraSharedClassListFile,
ClassListParser::_parse_all, CHECK);
}
}
// Rewrite and link classes
@ -993,8 +1037,8 @@ bool MetaspaceShared::write_static_archive(ArchiveBuilder* builder, FileMapInfo*
builder->write_archive(map_info, heap_info);
if (AllowArchivingWithJavaAgent) {
log_warning(cds)("This archive was created with AllowArchivingWithJavaAgent. It should be used "
"for testing purposes only and should not be used in a production environment");
log_warning(cds)("This %s was created with AllowArchivingWithJavaAgent. It should be used "
"for testing purposes only and should not be used in a production environment", CDSConfig::type_of_archive_being_loaded());
}
return true;
}
@ -1004,7 +1048,13 @@ bool MetaspaceShared::try_link_class(JavaThread* current, InstanceKlass* ik) {
ExceptionMark em(current);
JavaThread* THREAD = current; // For exception macros.
assert(CDSConfig::is_dumping_archive(), "sanity");
if (!ik->is_shared() && ik->is_loaded() && !ik->is_linked() && ik->can_be_verified_at_dumptime() &&
if (ik->is_shared() && !CDSConfig::is_dumping_final_static_archive()) {
assert(CDSConfig::is_dumping_dynamic_archive(), "must be");
return false;
}
if (ik->is_loaded() && !ik->is_linked() && ik->can_be_verified_at_dumptime() &&
!SystemDictionaryShared::has_class_failed_verification(ik)) {
bool saved = BytecodeVerificationLocal;
if (ik->is_shared_unregistered_class() && ik->class_loader() == nullptr) {
@ -1072,11 +1122,18 @@ bool MetaspaceShared::is_shared_static(void* p) {
// - When -XX:+RequireSharedSpaces is specified, AND the JVM cannot load the archive(s) due
// to version or classpath mismatch.
void MetaspaceShared::unrecoverable_loading_error(const char* message) {
log_error(cds)("An error has occurred while processing the shared archive file.");
log_error(cds)("An error has occurred while processing the %s.", CDSConfig::type_of_archive_being_loaded());
if (message != nullptr) {
log_error(cds)("%s", message);
}
vm_exit_during_initialization("Unable to use shared archive.", nullptr);
if (CDSConfig::is_dumping_final_static_archive()) {
vm_exit_during_initialization("Must be a valid AOT configuration generated by the current JVM", AOTConfiguration);
} else if (CDSConfig::new_aot_flags_used()) {
vm_exit_during_initialization("Unable to use AOT cache.", nullptr);
} else {
vm_exit_during_initialization("Unable to use shared archive.", nullptr);
}
}
// This function is called when the JVM is unable to write the specified CDS archive due to an

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2025, 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
@ -41,7 +41,13 @@ class Method;
class Symbol;
class RunTimeClassInfo {
public:
public:
enum : char {
FROM_FIELD_IS_PROTECTED = 1 << 0,
FROM_IS_ARRAY = 1 << 1,
FROM_IS_OBJECT = 1 << 2
};
struct CrcInfo {
int _clsfile_size;
int _clsfile_crc32;
@ -202,6 +208,17 @@ public:
return verifier_constraint_flags()[i];
}
bool from_field_is_protected(int i) {
return (verifier_constraint_flag(i) & FROM_FIELD_IS_PROTECTED) != 0;
}
bool from_is_array(int i) {
return (verifier_constraint_flag(i) & FROM_IS_ARRAY) != 0;
}
bool from_is_object(int i) {
return (verifier_constraint_flag(i) & FROM_IS_OBJECT) != 0;
}
int num_enum_klass_static_fields(int i) const {
return enum_klass_static_fields_addr()->_num;
}

View File

@ -413,7 +413,13 @@ ModuleEntry* ModuleEntry::allocate_archived_entry() const {
_archive_modules_entries->put(this, archived_entry);
DEBUG_ONLY(_num_archived_module_entries++);
assert(archived_entry->shared_protection_domain() == nullptr, "never set during -Xshare:dump");
if (CDSConfig::is_dumping_final_static_archive()) {
OopHandle null_handle;
archived_entry->_shared_pd = null_handle;
} else {
assert(archived_entry->shared_protection_domain() == nullptr, "never set during -Xshare:dump");
}
// Clear handles and restore at run time. Handles cannot be archived.
OopHandle null_handle;
archived_entry->_module = null_handle;

View File

@ -1177,6 +1177,10 @@ void SystemDictionary::load_shared_class_misc(InstanceKlass* ik, ClassLoaderData
// notify a class loaded from shared object
ClassLoadingService::notify_class_loaded(ik, true /* shared class */);
if (CDSConfig::is_dumping_final_static_archive()) {
SystemDictionaryShared::init_dumptime_info_from_preimage(ik);
}
}
#endif // INCLUDE_CDS

View File

@ -22,6 +22,7 @@
*
*/
#include "cds/aotClassLocation.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/archiveHeapLoader.hpp"
#include "cds/archiveUtils.hpp"
@ -193,23 +194,20 @@ InstanceKlass* SystemDictionaryShared::acquire_class_for_current_thread(
// k must not be a shared class.
DumpTimeClassInfo* SystemDictionaryShared::get_info(InstanceKlass* k) {
MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag);
assert(!k->is_shared(), "sanity");
return get_info_locked(k);
}
DumpTimeClassInfo* SystemDictionaryShared::get_info_locked(InstanceKlass* k) {
assert_lock_strong(DumpTimeTable_lock);
assert(!k->is_shared(), "sanity");
DumpTimeClassInfo* info = _dumptime_table->get_info(k);
assert(info != nullptr, "must be");
return info;
}
bool SystemDictionaryShared::check_for_exclusion(InstanceKlass* k, DumpTimeClassInfo* info) {
if (MetaspaceShared::is_in_shared_metaspace(k)) {
if (CDSConfig::is_dumping_dynamic_archive() && MetaspaceShared::is_in_shared_metaspace(k)) {
// We have reached a super type that's already in the base archive. Treat it
// as "not excluded".
assert(CDSConfig::is_dumping_dynamic_archive(), "must be");
return false;
}
@ -277,6 +275,11 @@ bool SystemDictionaryShared::is_hidden_lambda_proxy(InstanceKlass* ik) {
}
bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) {
if (CDSConfig::is_dumping_final_static_archive() && k->is_shared_unregistered_class()
&& k->is_shared()) {
return false; // Do not exclude: unregistered classes are passed from preimage to final image.
}
if (k->is_in_error_state()) {
return warn_excluded(k, "In error state");
}
@ -329,6 +332,10 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) {
// class may fail to verify in AOTLinkedClassBulkLoader::init_required_classes_for_loader(),
// causing the JVM to fail at bootstrap.
return warn_excluded(k, "Unlinked class not supported by AOTClassLinking");
} else if (CDSConfig::is_dumping_preimage_static_archive()) {
// When dumping the final static archive, we will unconditionally load and link all
// classes from tje preimage. We don't want to get a VerifyError when linking this class.
return warn_excluded(k, "Unlinked class not supported by AOTConfiguration");
}
} else {
if (!k->can_be_verified_at_dumptime()) {
@ -497,6 +504,9 @@ void SystemDictionaryShared::initialize() {
_dumptime_table = new (mtClass) DumpTimeSharedClassTable;
_dumptime_lambda_proxy_class_dictionary =
new (mtClass) DumpTimeLambdaProxyClassDictionary;
if (CDSConfig::is_dumping_heap()) {
HeapShared::init_dumping();
}
}
}
@ -537,6 +547,18 @@ void SystemDictionaryShared::handle_class_unloading(InstanceKlass* klass) {
}
}
void SystemDictionaryShared::init_dumptime_info_from_preimage(InstanceKlass* k) {
init_dumptime_info(k);
copy_verification_constraints_from_preimage(k);
copy_linking_constraints_from_preimage(k);
if (SystemDictionary::is_platform_class_loader(k->class_loader())) {
AOTClassLocationConfig::dumptime_set_has_platform_classes();
} else if (SystemDictionary::is_system_class_loader(k->class_loader())) {
AOTClassLocationConfig::dumptime_set_has_app_classes();
}
}
// Check if a class or any of its supertypes has been redefined.
bool SystemDictionaryShared::has_been_redefined(InstanceKlass* k) {
if (k->has_been_redefined()) {
@ -723,7 +745,10 @@ void SystemDictionaryShared::dumptime_classes_do(class MetaspaceClosure* it) {
assert_lock_strong(DumpTimeTable_lock);
auto do_klass = [&] (InstanceKlass* k, DumpTimeClassInfo& info) {
if (k->is_loader_alive() && !info.is_excluded()) {
if (CDSConfig::is_dumping_final_static_archive() && !k->is_loaded()) {
assert(k->is_shared_unregistered_class(), "must be");
info.metaspace_pointers_do(it);
} else if (k->is_loader_alive() && !info.is_excluded()) {
info.metaspace_pointers_do(it);
}
};
@ -795,6 +820,10 @@ void SystemDictionaryShared::add_lambda_proxy_class(InstanceKlass* caller_ik,
// There's no need to remember them in a separate table.
return;
}
if (CDSConfig::is_dumping_preimage_static_archive()) {
// Information about lambda proxies are recorded in FinalImageRecipes.
return;
}
assert(caller_ik->class_loader() == lambda_ik->class_loader(), "mismatched class loader");
assert(caller_ik->class_loader_data() == lambda_ik->class_loader_data(), "mismatched class loader data");
@ -832,6 +861,10 @@ InstanceKlass* SystemDictionaryShared::get_shared_lambda_proxy_class(InstanceKla
Symbol* method_type,
Method* member_method,
Symbol* instantiated_method_type) {
if (CDSConfig::is_dumping_final_static_archive()) {
return nullptr;
}
assert(caller_ik != nullptr, "sanity");
assert(invoked_name != nullptr, "sanity");
assert(invoked_type != nullptr, "sanity");
@ -956,7 +989,7 @@ InstanceKlass* SystemDictionaryShared::prepare_shared_lambda_proxy_class(Instanc
void SystemDictionaryShared::check_verification_constraints(InstanceKlass* klass,
TRAPS) {
assert(!CDSConfig::is_dumping_static_archive() && CDSConfig::is_using_archive(), "called at run time with CDS enabled only");
assert(CDSConfig::is_using_archive(), "called at run time with CDS enabled only");
RunTimeClassInfo* record = RunTimeClassInfo::get_for(klass);
int length = record->num_verifier_constraints();
@ -965,21 +998,16 @@ void SystemDictionaryShared::check_verification_constraints(InstanceKlass* klass
RunTimeClassInfo::RTVerifierConstraint* vc = record->verifier_constraint_at(i);
Symbol* name = vc->name();
Symbol* from_name = vc->from_name();
char c = record->verifier_constraint_flag(i);
if (log_is_enabled(Trace, cds, verification)) {
ResourceMark rm(THREAD);
log_trace(cds, verification)("check_verification_constraint: %s: %s must be subclass of %s [0x%x]",
klass->external_name(), from_name->as_klass_external_name(),
name->as_klass_external_name(), c);
name->as_klass_external_name(), record->verifier_constraint_flag(i));
}
bool from_field_is_protected = (c & SystemDictionaryShared::FROM_FIELD_IS_PROTECTED) ? true : false;
bool from_is_array = (c & SystemDictionaryShared::FROM_IS_ARRAY) ? true : false;
bool from_is_object = (c & SystemDictionaryShared::FROM_IS_OBJECT) ? true : false;
bool ok = VerificationType::resolve_and_check_assignability(klass, name,
from_name, from_field_is_protected, from_is_array, from_is_object, CHECK);
bool ok = VerificationType::resolve_and_check_assignability(klass, name, from_name,
record->from_field_is_protected(i), record->from_is_array(i), record->from_is_object(i), CHECK);
if (!ok) {
ResourceMark rm(THREAD);
stringStream ss;
@ -995,6 +1023,24 @@ void SystemDictionaryShared::check_verification_constraints(InstanceKlass* klass
}
}
void SystemDictionaryShared::copy_verification_constraints_from_preimage(InstanceKlass* klass) {
assert(CDSConfig::is_using_archive(), "called at run time with CDS enabled only");
DumpTimeClassInfo* dt_info = get_info(klass);
RunTimeClassInfo* rt_info = RunTimeClassInfo::get_for(klass); // from preimage
int length = rt_info->num_verifier_constraints();
if (length > 0) {
for (int i = 0; i < length; i++) {
RunTimeClassInfo::RTVerifierConstraint* vc = rt_info->verifier_constraint_at(i);
Symbol* name = vc->name();
Symbol* from_name = vc->from_name();
dt_info->add_verification_constraint(klass, name, from_name,
rt_info->from_field_is_protected(i), rt_info->from_is_array(i), rt_info->from_is_object(i));
}
}
}
static oop get_class_loader_by(char type) {
if (type == (char)ClassLoader::BOOT_LOADER) {
return (oop)nullptr;
@ -1066,7 +1112,7 @@ void SystemDictionaryShared::record_linking_constraint(Symbol* name, InstanceKla
// returns true IFF there's no need to re-initialize the i/v-tables for klass for
// the purpose of checking class loader constraints.
bool SystemDictionaryShared::check_linking_constraints(Thread* current, InstanceKlass* klass) {
assert(!CDSConfig::is_dumping_static_archive() && CDSConfig::is_using_archive(), "called at run time with CDS enabled only");
assert(CDSConfig::is_using_archive(), "called at run time with CDS enabled only");
LogTarget(Info, class, loader, constraints) log;
if (klass->is_shared_boot_class()) {
// No class loader constraint check performed for boot classes.
@ -1112,6 +1158,24 @@ bool SystemDictionaryShared::check_linking_constraints(Thread* current, Instance
return false;
}
void SystemDictionaryShared::copy_linking_constraints_from_preimage(InstanceKlass* klass) {
assert(CDSConfig::is_using_archive(), "called at run time with CDS enabled only");
JavaThread* current = JavaThread::current();
if (klass->is_shared_platform_class() || klass->is_shared_app_class()) {
RunTimeClassInfo* rt_info = RunTimeClassInfo::get_for(klass); // from preimage
if (rt_info->num_loader_constraints() > 0) {
for (int i = 0; i < rt_info->num_loader_constraints(); i++) {
RunTimeClassInfo::RTLoaderConstraint* lc = rt_info->loader_constraint_at(i);
Symbol* name = lc->constraint_name();
Handle loader1(current, get_class_loader_by(lc->_loader_type1));
Handle loader2(current, get_class_loader_by(lc->_loader_type2));
record_linking_constraint(name, klass, loader1, loader2);
}
}
}
}
bool SystemDictionaryShared::is_supported_invokedynamic(BootstrapInfo* bsi) {
LogTarget(Debug, cds, lambda) log;
if (bsi->arg_values() == nullptr || !bsi->arg_values()->is_objArray()) {

View File

@ -152,13 +152,6 @@ class SystemDictionaryShared: public SystemDictionary {
void print_table_statistics(const char* prefix, outputStream* st);
};
public:
enum : char {
FROM_FIELD_IS_PROTECTED = 1 << 0,
FROM_IS_ARRAY = 1 << 1,
FROM_IS_OBJECT = 1 << 2
};
private:
static DumpTimeSharedClassTable* _dumptime_table;
@ -199,6 +192,9 @@ private:
static InstanceKlass* retrieve_lambda_proxy_class(const RunTimeLambdaProxyClassInfo* info) NOT_CDS_RETURN_(nullptr);
DEBUG_ONLY(static bool _class_loading_may_happen;)
static void copy_verification_constraints_from_preimage(InstanceKlass* klass);
static void copy_linking_constraints_from_preimage(InstanceKlass* klass);
public:
static bool is_registered_lambda_proxy_class(InstanceKlass* ik);
static bool is_hidden_lambda_proxy(InstanceKlass* ik);
@ -229,6 +225,7 @@ public:
static void initialize() NOT_CDS_RETURN;
static void init_dumptime_info(InstanceKlass* k) NOT_CDS_RETURN;
static void init_dumptime_info_from_preimage(InstanceKlass* k) NOT_CDS_RETURN;
static void handle_class_unloading(InstanceKlass* k) NOT_CDS_RETURN;
static Dictionary* boot_loader_dictionary() {

View File

@ -38,6 +38,7 @@
#define NUM_CDS_REGIONS 4 // this must be the same as MetaspaceShared::n_regions
#define CDS_ARCHIVE_MAGIC 0xf00baba2
#define CDS_DYNAMIC_ARCHIVE_MAGIC 0xf00baba8
#define CDS_PREIMAGE_ARCHIVE_MAGIC 0xcafea07c
#define CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION 13
#define CURRENT_CDS_ARCHIVE_VERSION 18

View File

@ -716,7 +716,6 @@ void Metaspace::global_initialize() {
metaspace::ChunkHeaderPool::initialize();
if (CDSConfig::is_dumping_static_archive()) {
assert(!CDSConfig::is_using_archive(), "sanity");
MetaspaceShared::initialize_for_static_dump();
}

View File

@ -454,6 +454,11 @@ void ConstantPool::restore_unshareable_info(TRAPS) {
}
}
}
if (CDSConfig::is_dumping_final_static_archive() && resolved_references() != nullptr) {
objArrayOop scratch_references = oopFactory::new_objArray(vmClasses::Object_klass(), resolved_references()->length(), CHECK);
HeapShared::add_scratch_resolved_references(this, scratch_references);
}
}
void ConstantPool::remove_unshareable_info() {

View File

@ -2707,6 +2707,7 @@ void InstanceKlass::remove_unshareable_info() {
}
init_shared_package_entry();
_dep_context_last_cleaned = 0;
DEBUG_ONLY(_shared_class_load_count = 0);
remove_unshareable_flags();
}

View File

@ -1365,11 +1365,19 @@ void Arguments::set_mode_flags(Mode mode) {
// incompatible command line options were chosen.
void Arguments::no_shared_spaces(const char* message) {
if (RequireSharedSpaces) {
jio_fprintf(defaultStream::error_stream(),
"Class data sharing is inconsistent with other specified options.\n");
vm_exit_during_initialization("Unable to use shared archive", message);
log_error(cds)("%s is incompatible with other specified options.",
CDSConfig::new_aot_flags_used() ? "AOT cache" : "CDS");
if (CDSConfig::new_aot_flags_used()) {
vm_exit_during_initialization("Unable to use AOT cache", message);
} else {
vm_exit_during_initialization("Unable to use shared archive", message);
}
} else {
log_info(cds)("Unable to use shared archive: %s", message);
if (CDSConfig::new_aot_flags_used()) {
log_warning(cds)("Unable to use AOT cache: %s", message);
} else {
log_info(cds)("Unable to use shared archive: %s", message);
}
UseSharedSpaces = false;
}
}

View File

@ -23,6 +23,7 @@
*/
#include "cds/cds_globals.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/classListWriter.hpp"
#include "cds/dynamicArchive.hpp"
#include "classfile/classLoader.hpp"
@ -440,6 +441,10 @@ void before_exit(JavaThread* thread, bool halt) {
#if INCLUDE_CDS
ClassListWriter::write_resolved_constants();
if (CDSConfig::is_dumping_preimage_static_archive()) {
MetaspaceShared::preload_and_dump(thread);
}
#endif
// Hang forever on exit if we're reporting an error.

View File

@ -853,7 +853,11 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
_vm_complete = true;
#endif
if (CDSConfig::is_dumping_static_archive()) {
if (CDSConfig::is_dumping_classic_static_archive()) {
// Classic -Xshare:dump, aka "old workflow"
MetaspaceShared::preload_and_dump(CHECK_JNI_ERR);
} else if (CDSConfig::is_dumping_final_static_archive()) {
tty->print_cr("Reading AOTConfiguration %s and writing AOTCache %s", AOTConfiguration, AOTCache);
MetaspaceShared::preload_and_dump(CHECK_JNI_ERR);
}

View File

@ -405,6 +405,7 @@ hotspot_cds_only = \
hotspot_appcds_dynamic = \
runtime/cds/appcds/ \
-runtime/cds/appcds/aotClassLinking \
-runtime/cds/appcds/applications \
-runtime/cds/appcds/cacheObject \
-runtime/cds/appcds/complexURI \
@ -423,7 +424,6 @@ hotspot_appcds_dynamic = \
-runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java \
-runtime/cds/appcds/lambdaForm/DefaultClassListLFInvokers.java \
-runtime/cds/appcds/methodHandles \
-runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java \
-runtime/cds/appcds/sharedStrings \
-runtime/cds/appcds/resolvedConstants \
-runtime/cds/appcds/ArchiveRelocationTest.java \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2025, 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
@ -50,7 +50,7 @@ public class ArchiveDoesNotExist {
// -Xshare=on
OutputAnalyzer out = CDSTestUtils.runWithArchive(opts);
CDSTestUtils.checkMappingFailure(out);
out.shouldContain("Specified shared archive not found")
out.shouldContain("Specified shared archive file not found")
.shouldHaveExitValue(1);
// -Xshare=auto

View File

@ -49,17 +49,21 @@ public class AOTFlags {
}
static void positiveTests() throws Exception {
// (1) Training Run
//----------------------------------------------------------------------
printTestCase("Training Run");
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=record",
"-XX:AOTConfiguration=" + aotConfigFile,
"-Xlog:cds=debug",
"-cp", appJar, helloClass);
OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "train");
out.shouldContain("Hello World");
out.shouldContain("AOTConfiguration recorded: " + aotConfigFile);
out.shouldHaveExitValue(0);
// (2) Assembly Phase (AOTClassLinking unspecified -> should be enabled by default)
//----------------------------------------------------------------------
printTestCase("Assembly Phase (AOTClassLinking unspecified -> should be enabled by default)");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=create",
"-XX:AOTConfiguration=" + aotConfigFile,
@ -71,18 +75,20 @@ public class AOTFlags {
out.shouldMatch("cds.*hello[.]aot");
out.shouldHaveExitValue(0);
// (3) Production Run with AOTCache
//----------------------------------------------------------------------
printTestCase("Production Run with AOTCache");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTCache=" + aotCacheFile,
"-Xlog:cds",
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "prod");
out.shouldContain("Using AOT-linked classes: true (static archive: has aot-linked classes)");
out.shouldContain("Opened archive hello.aot.");
out.shouldContain("Opened AOT cache hello.aot.");
out.shouldContain("Hello World");
out.shouldHaveExitValue(0);
// (4) AOTMode=off
//----------------------------------------------------------------------
printTestCase("AOTMode=off");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTCache=" + aotCacheFile,
"--show-version",
@ -91,11 +97,12 @@ public class AOTFlags {
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "prod");
out.shouldNotContain(", sharing");
out.shouldNotContain("Opened archive hello.aot.");
out.shouldNotContain("Opened AOT cache hello.aot.");
out.shouldContain("Hello World");
out.shouldHaveExitValue(0);
// (5) AOTMode=auto
//----------------------------------------------------------------------
printTestCase("AOTMode=auto");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTCache=" + aotCacheFile,
"--show-version",
@ -104,11 +111,12 @@ public class AOTFlags {
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "prod");
out.shouldContain(", sharing");
out.shouldContain("Opened archive hello.aot.");
out.shouldContain("Opened AOT cache hello.aot.");
out.shouldContain("Hello World");
out.shouldHaveExitValue(0);
// (6) AOTMode=on
//----------------------------------------------------------------------
printTestCase("AOTMode=on");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTCache=" + aotCacheFile,
"--show-version",
@ -117,11 +125,12 @@ public class AOTFlags {
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "prod");
out.shouldContain(", sharing");
out.shouldContain("Opened archive hello.aot.");
out.shouldContain("Opened AOT cache hello.aot.");
out.shouldContain("Hello World");
out.shouldHaveExitValue(0);
// (7) Assembly Phase with -XX:-AOTClassLinking
//----------------------------------------------------------------------
printTestCase("Assembly Phase with -XX:-AOTClassLinking");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=create",
"-XX:-AOTClassLinking",
@ -134,20 +143,22 @@ public class AOTFlags {
out.shouldMatch("cds.*hello[.]aot");
out.shouldHaveExitValue(0);
// (8) Production Run with AOTCache, which was created with -XX:-AOTClassLinking
//----------------------------------------------------------------------
printTestCase("Production Run with AOTCache, which was created with -XX:-AOTClassLinking");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTCache=" + aotCacheFile,
"-Xlog:cds",
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "prod");
out.shouldContain("Using AOT-linked classes: false (static archive: no aot-linked classes)");
out.shouldContain("Opened archive hello.aot.");
out.shouldContain("Opened AOT cache hello.aot.");
out.shouldContain("Hello World");
out.shouldHaveExitValue(0);
}
static void negativeTests() throws Exception {
// (1) Mixing old and new options
//----------------------------------------------------------------------
printTestCase("Mixing old and new options");
String mixOldNewErrSuffix = " cannot be used at the same time with -Xshare:on, -Xshare:auto, "
+ "-Xshare:off, -Xshare:dump, DumpLoadedClassList, SharedClassListFile, "
+ "or SharedArchiveFile";
@ -169,7 +180,8 @@ public class AOTFlags {
out.shouldContain("Option AOTCache" + mixOldNewErrSuffix);
out.shouldNotHaveExitValue(0);
// (2) Use AOTConfiguration without AOTMode
//----------------------------------------------------------------------
printTestCase("Use AOTConfiguration without AOTMode");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTConfiguration=" + aotConfigFile,
"-cp", appJar, helloClass);
@ -178,7 +190,8 @@ public class AOTFlags {
out.shouldContain("AOTConfiguration can only be used with -XX:AOTMode=record or -XX:AOTMode=create");
out.shouldNotHaveExitValue(0);
// (3) Use AOTMode without AOTConfiguration
//----------------------------------------------------------------------
printTestCase("Use AOTMode without AOTConfiguration");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=record",
"-cp", appJar, helloClass);
@ -194,7 +207,8 @@ public class AOTFlags {
out.shouldContain("-XX:AOTMode=create cannot be used without setting AOTConfiguration");
out.shouldNotHaveExitValue(0);
// (4) Bad AOTMode
//----------------------------------------------------------------------
printTestCase("Bad AOTMode");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=foo",
"-cp", appJar, helloClass);
@ -203,7 +217,8 @@ public class AOTFlags {
out.shouldContain("Unrecognized value foo for AOTMode. Must be one of the following: off, record, create, auto, on");
out.shouldNotHaveExitValue(0);
// (5) AOTCache specified with -XX:AOTMode=record
//----------------------------------------------------------------------
printTestCase("AOTCache specified with -XX:AOTMode=record");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=record",
"-XX:AOTConfiguration=" + aotConfigFile,
@ -214,7 +229,8 @@ public class AOTFlags {
out.shouldContain("AOTCache must not be specified when using -XX:AOTMode=record");
out.shouldNotHaveExitValue(0);
// (5) AOTCache not specified with -XX:AOTMode=create
//----------------------------------------------------------------------
printTestCase("AOTCache not specified with -XX:AOTMode=create");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=create",
"-XX:AOTConfiguration=" + aotConfigFile,
@ -224,5 +240,69 @@ public class AOTFlags {
out.shouldContain("AOTCache must be specified when using -XX:AOTMode=create");
out.shouldNotHaveExitValue(0);
//----------------------------------------------------------------------
printTestCase("No such config file");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=create",
"-XX:AOTConfiguration=no-such-file",
"-XX:AOTCache=" + aotCacheFile,
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "neg");
out.shouldContain("Must be a valid AOT configuration generated by the current JVM: no-such-file");
out.shouldNotHaveExitValue(0);
//----------------------------------------------------------------------
printTestCase("AOTConfiguration file cannot be used as a CDS archive");
// first make sure we have a valid aotConfigFile
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=record",
"-XX:AOTConfiguration=" + aotConfigFile,
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "train");
out.shouldHaveExitValue(0);
// Cannot use this config file as a AOT cache
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=on",
"-XX:AOTCache=" + aotConfigFile,
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "neg");
out.shouldContain("Not a valid AOT cache (hello.aotconfig)");
out.shouldNotHaveExitValue(0);
// Cannot use this config file as a CDS archive
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-Xshare:on",
"-XX:SharedArchiveFile=" + aotConfigFile,
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "neg");
out.shouldContain("Not a valid archive (hello.aotconfig)");
out.shouldNotHaveExitValue(0);
//----------------------------------------------------------------------
printTestCase("Classpath mismatch when creating archive");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTMode=create",
"-XX:AOTConfiguration=" + aotConfigFile,
"-XX:AOTCache=" + aotCacheFile,
"-cp", "noSuchJar.jar");
out = CDSTestUtils.executeAndLog(pb, "neg");
out.shouldContain("class path and/or module path are not compatible with the ones " +
"specified when the AOTConfiguration file was recorded");
out.shouldContain("Unable to use create AOT cache");
out.shouldHaveExitValue(1);
}
static int testNum = 0;
static void printTestCase(String s) {
System.out.println("vvvvvvv TEST CASE " + testNum + ": " + s + " starts here vvvvvvv");
testNum++;
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2025, 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.
*
*/
/*
* @test Make sure loader constraints are passed from AOT preimage to final image.
* @bug 8348426
* @requires vm.cds.supports.aot.class.linking
* @comment work around JDK-8345635
* @requires !vm.jvmci.enabled
* @library /test/jdk/lib/testlibrary /test/lib
* @build AOTLoaderConstraintsTest BootClass
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar BootClass
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar AOTLoaderConstraintsTestApp.jar AOTLoaderConstraintsTestApp AppClass
* @run driver AOTLoaderConstraintsTest AOT
*/
import jdk.test.lib.cds.CDSAppTester;
import jdk.test.lib.helpers.ClassFileInstaller;
import jdk.test.lib.process.OutputAnalyzer;
public class AOTLoaderConstraintsTest {
static final String appJar = ClassFileInstaller.getJarPath("AOTLoaderConstraintsTestApp.jar");
static final String mainClass = "AOTLoaderConstraintsTestApp";
public static void main(String[] args) throws Exception {
Tester t = new Tester();
t.run(args);
}
static class Tester extends CDSAppTester {
public Tester() {
super(mainClass);
}
@Override
public String classpath(RunMode runMode) {
return appJar;
}
@Override
public String[] vmArgs(RunMode runMode) {
return new String[] {
"-Xbootclasspath/a:boot.jar",
"-Xlog:class+loader+constraints=debug",
"-Xlog:class+path=debug",
};
}
@Override
public String[] appCommandLine(RunMode runMode) {
return new String[] {
mainClass,
};
}
@Override
public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
switch (runMode) {
case RunMode.ASSEMBLY: // JEP 485 + binary AOTConfiguration -- should load AppClass from preimage
case RunMode.PRODUCTION:
out.shouldContain("CDS add loader constraint for class AppClass symbol java/lang/String loader[0] 'app' loader[1] 'bootstrap'");
}
}
}
}
class AOTLoaderConstraintsTestApp {
public static void main(String args[]) throws Exception {
AppClass obj = new AppClass();
obj.func("Hello");
}
}
class AppClass extends BootClass {
@Override
public void func(String s) {
// This method overrides BootClass, which is loaded by the boot loader.
// AppClass is loaded by the app loader. To make sure that you cannot use
// type masquerade attacks, we need to add a loader constraint that says:
// app and boot loaders must resolve the symbol "java/lang/String" to the same type.
super.func(s + " From AppClass");
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2025, 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.
*
*/
// This is a test class to be loaded by the boot loader.
public class BootClass {
public void func(String s) {
System.out.println(s);
}
}

View File

@ -53,6 +53,19 @@
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. BulkLoaderTest DYNAMIC
*/
/*
* @test id=aot
* @requires vm.cds.supports.aot.class.linking
* @comment work around JDK-8345635
* @requires !vm.jvmci.enabled
* @library /test/jdk/lib/testlibrary /test/lib
* @build InitiatingLoaderTester BadOldClassA BadOldClassB
* @build BulkLoaderTest
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester
* BadOldClassA BadOldClassB
* @run driver BulkLoaderTest AOT
*/
import java.io.File;
import java.lang.StackWalker.StackFrame;
import java.util.List;
@ -122,6 +135,13 @@ public class BulkLoaderTest {
mainClass,
};
}
@Override
public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
if (isAOTWorkflow() && runMode == RunMode.TRAINING) {
out.shouldContain("Skipping BadOldClassA: Unlinked class not supported by AOTConfiguration");
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, 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
@ -40,6 +40,15 @@
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. JavacBench DYNAMIC
*/
/*
* @test id=aot
* @requires vm.cds.supports.aot.class.linking
* @summary Run JavacBenchApp with AOT cache (JEP 483)
* @requires vm.cds
* @library /test/lib
* @run driver JavacBench AOT
*/
import jdk.test.lib.cds.CDSAppTester;
import jdk.test.lib.helpers.ClassFileInstaller;

View File

@ -188,7 +188,7 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
nonExistBaseFile.delete();
runTwo(nonExistBase, topArchiveName,
appJar, mainClass, isAuto ? 0 : 1,
"Specified shared archive not found (" + nonExistBase + ")");
"Specified shared archive file not found (" + nonExistBase + ")");
startTest("9. Non-exist top archive");
String nonExistTop = "non-exist-top.jsa";
@ -196,11 +196,11 @@ public class ArchiveConsistency extends DynamicArchiveTestBase {
nonExistTopFile.delete();
runTwo(baseArchiveName, nonExistTop,
appJar, mainClass, isAuto ? 0 : 1,
"Specified shared archive not found (" + nonExistTop + ")");
"Specified shared archive file not found (" + nonExistTop + ")");
startTest("10. nost-exist-base and non-exist-top");
runTwo(nonExistBase, nonExistTop,
appJar, mainClass, isAuto ? 0 : 1,
"Specified shared archive not found (" + nonExistBase + ")");
"Specified shared archive file not found (" + nonExistBase + ")");
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, 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
@ -663,7 +663,7 @@ public class TestAutoCreateSharedArchive extends DynamicArchiveTestBase {
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldContain("Specified shared archive not found (" + nonExistTop + ")")
output.shouldContain("Specified shared archive file not found (" + nonExistTop + ")")
.shouldContain(HELLO_WORLD)
.shouldContain("Dumping shared data to file:");
});
@ -688,7 +688,7 @@ public class TestAutoCreateSharedArchive extends DynamicArchiveTestBase {
"-cp", appJar,
mainAppClass)
.assertNormalExit(output -> {
output.shouldContain("Specified shared archive not found (" + nonExistBase + ")")
output.shouldContain("Specified shared archive file not found (" + nonExistBase + ")")
.shouldContain(HELLO_WORLD)
.shouldNotContain("Dumping shared data to file:");
});

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, 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
@ -162,7 +162,7 @@ public class TestAutoCreateSharedArchiveUpgrade {
}
static void assertJSANotFound(OutputAnalyzer output) {
output.shouldContain("Specified shared archive not found");
output.shouldContain("Specified shared archive file not found");
}
static void assertCreatedJSA(OutputAnalyzer output) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2025, 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
@ -48,13 +48,12 @@ public class DumpingWithJavaAgent {
};
public static String warningMessages[] = {
"This archive was created with AllowArchivingWithJavaAgent",
"This shared archive file was created with AllowArchivingWithJavaAgent",
"It should be used for testing purposes only and should not be used in a production environment",
};
public static String errorMessage =
"The setting of the AllowArchivingWithJavaAgent is different from the setting in the shared archive.";
"The setting of the AllowArchivingWithJavaAgent is different from the setting in the shared archive file.";
public static String diagnosticOption = "-XX:+AllowArchivingWithJavaAgent";

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, 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
@ -40,8 +40,12 @@ abstract public class CDSAppTester {
private final String name;
private final String classListFile;
private final String classListFileLog;
private final String aotConfigurationFile;
private final String aotConfigurationFileLog;
private final String staticArchiveFile;
private final String staticArchiveFileLog;
private final String aotCacheFile;
private final String aotCacheFileLog;
private final String dynamicArchiveFile;
private final String dynamicArchiveFileLog;
private final String tempBaseArchiveFile;
@ -56,8 +60,12 @@ abstract public class CDSAppTester {
this.name = name;
classListFile = name() + ".classlist";
classListFileLog = classListFile + ".log";
aotConfigurationFile = name() + ".aotconfig";
aotConfigurationFileLog = aotConfigurationFile + ".log";
staticArchiveFile = name() + ".static.jsa";
staticArchiveFileLog = staticArchiveFile + ".log";
aotCacheFile = name() + ".aot";
aotCacheFileLog = aotCacheFile + ".log";
dynamicArchiveFile = name() + ".dynamic.jsa";
dynamicArchiveFileLog = dynamicArchiveFile + ".log";
tempBaseArchiveFile = name() + ".temp-base.jsa";
@ -74,13 +82,15 @@ abstract public class CDSAppTester {
private enum Workflow {
STATIC, // classic -Xshare:dump workflow
DYNAMIC, // classic -XX:ArchiveClassesAtExit
AOT, // JEP 483 Ahead-of-Time Class Loading & Linking
}
public enum RunMode {
CLASSLIST,
DUMP_STATIC,
DUMP_DYNAMIC,
PRODUCTION;
TRAINING, // -XX:DumpLoadedClassList OR {-XX:AOTMode=create -XX:AOTConfiguration}
DUMP_STATIC, // -Xshare:dump
DUMP_DYNAMIC, // -XX:ArchiveClassesArExit
ASSEMBLY, // JEP 483
PRODUCTION; // Running with the CDS archive produced from the above steps
public boolean isStaticDump() {
return this == DUMP_STATIC;
@ -126,6 +136,10 @@ abstract public class CDSAppTester {
return workflow == Workflow.DYNAMIC;
}
public final boolean isAOTWorkflow() {
return workflow == Workflow.AOT;
}
private String logToFile(String logFile, String... logTags) {
StringBuilder sb = new StringBuilder("-Xlog:");
String prefix = "";
@ -163,8 +177,20 @@ abstract public class CDSAppTester {
return output;
}
private OutputAnalyzer recordAOTConfiguration() throws Exception {
RunMode runMode = RunMode.TRAINING;
String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
"-XX:AOTMode=record",
"-XX:AOTConfiguration=" + aotConfigurationFile,
"-cp", classpath(runMode),
logToFile(aotConfigurationFileLog,
"class+load=debug"));
cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
return executeAndCheck(cmdLine, runMode, aotConfigurationFile, aotConfigurationFileLog);
}
private OutputAnalyzer createClassList() throws Exception {
RunMode runMode = RunMode.CLASSLIST;
RunMode runMode = RunMode.TRAINING;
String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
"-Xshare:off",
"-XX:DumpLoadedClassList=" + classListFile,
@ -192,6 +218,23 @@ abstract public class CDSAppTester {
return executeAndCheck(cmdLine, runMode, staticArchiveFile, staticArchiveFileLog);
}
private OutputAnalyzer createAOTCache() throws Exception {
RunMode runMode = RunMode.ASSEMBLY;
String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
"-Xlog:cds",
"-Xlog:cds+heap=error",
"-XX:AOTMode=create",
"-XX:AOTConfiguration=" + aotConfigurationFile,
"-XX:AOTCache=" + aotCacheFile,
"-cp", classpath(runMode),
logToFile(aotCacheFileLog,
"cds=debug",
"cds+class=debug",
"cds+heap=warning",
"cds+resolve=debug"));
return executeAndCheck(cmdLine, runMode, aotCacheFile, aotCacheFileLog);
}
// Creating a dynamic CDS archive (with -XX:ArchiveClassesAtExit=<foo>.jsa) requires that the current
// JVM process is using a static archive (which is usually the default CDS archive included in the JDK).
// However, if the JDK doesn't include a default CDS archive that's compatible with the set of
@ -264,6 +307,8 @@ abstract public class CDSAppTester {
cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + staticArchiveFile);
} else if (isDynamicWorkflow()) {
cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + dynamicArchiveFile);
} else if (isAOTWorkflow()) {
cmdLine = StringArrayUtils.concat(cmdLine, "-XX:AOTMode=on", "-XX:AOTCache=" + aotCacheFile);
}
if (extraVmArgs != null) {
@ -296,6 +341,8 @@ abstract public class CDSAppTester {
runStaticWorkflow();
} else if (args[0].equals("DYNAMIC")) {
runDynamicWorkflow();
} else if (args[0].equals("AOT")) {
runAOTWorkflow();
} else {
throw new RuntimeException(err);
}
@ -314,4 +361,12 @@ abstract public class CDSAppTester {
dumpDynamicArchive();
productionRun();
}
// See JEP 485
private void runAOTWorkflow() throws Exception {
this.workflow = Workflow.AOT;
recordAOTConfiguration();
createAOTCache();
productionRun();
}
}