diff --git a/src/hotspot/share/cds/aotClassLocation.cpp b/src/hotspot/share/cds/aotClassLocation.cpp index 8d453fe1773..c083213b933 100644 --- a/src/hotspot/share/cds/aotClassLocation.cpp +++ b/src/hotspot/share/cds/aotClassLocation.cpp @@ -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); } diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index afd2d909595..21e97457a87 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -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 || diff --git a/src/hotspot/share/cds/archiveUtils.hpp b/src/hotspot/share/cds/archiveUtils.hpp index a10117e9f9a..59146547aca 100644 --- a/src/hotspot/share/cds/archiveUtils.hpp +++ b/src/hotspot/share/cds/archiveUtils.hpp @@ -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 static Array* archive_non_ptr_array(GrowableArray* tmp_array); + template static Array* archive_ptr_array(GrowableArray* 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 static Array* archive_array(GrowableArray* tmp_array); + + template ::value)> + static Array* archive_array(GrowableArray* tmp_array) { + return archive_non_ptr_array(tmp_array); + } + + template ::value)> + static Array* archive_array(GrowableArray* 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()). diff --git a/src/hotspot/share/cds/archiveUtils.inline.hpp b/src/hotspot/share/cds/archiveUtils.inline.hpp index 537b3d1670c..9388bca18c7 100644 --- a/src/hotspot/share/cds/archiveUtils.inline.hpp +++ b/src/hotspot/share/cds/archiveUtils.inline.hpp @@ -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 that's allocated in the ArchiveBuilder "buffer" space. template -Array* ArchiveUtils::archive_array(GrowableArray* tmp_array) { +Array* ArchiveUtils::archive_non_ptr_array(GrowableArray* tmp_array) { + ArchiveBuilder* builder = ArchiveBuilder::current(); + Array* archived_array = ArchiveBuilder::new_ro_array(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::value) { + } + + return archived_array; +} + +// Returns the address of an Array 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 +Array* ArchiveUtils::archive_ptr_array(GrowableArray* tmp_array) { + ArchiveBuilder* builder = ArchiveBuilder::current(); + const bool is_dynamic_dump = CDSConfig::is_dumping_dynamic_archive(); + + Array* archived_array = ArchiveBuilder::new_ro_array(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; diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 564298fa5c8..1bb842af953 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -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; diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index c2dc2b41a93..d9f5a593098 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -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) diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index 811740cfbcb..bb9ffe2044b 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -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") \ diff --git a/src/hotspot/share/cds/cppVtables.cpp b/src/hotspot/share/cds/cppVtables.cpp index 39849571015..b8243cedf6d 100644 --- a/src/hotspot/share/cds/cppVtables.cpp +++ b/src/hotspot/share/cds/cppVtables.cpp @@ -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 void CppVtableCloner::init_orig_cpp_vtptr(int kind) { @@ -212,15 +222,27 @@ void CppVtableCloner::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]; } diff --git a/src/hotspot/share/cds/dumpTimeClassInfo.cpp b/src/hotspot/share/cds/dumpTimeClassInfo.cpp index 18136d6eeec..94a0f14ff1f 100644 --- a/src/hotspot/share/cds/dumpTimeClassInfo.cpp +++ b/src/hotspot/share/cds/dumpTimeClassInfo.cpp @@ -66,9 +66,9 @@ void DumpTimeClassInfo::add_verification_constraint(InstanceKlass* k, Symbol* na GrowableArray* 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"); diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index 4ccf23ff91c..095b443af66 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -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(); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 7db8e78743e..8a7e66c19e4 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -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"); } diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 22f70635e17..25550d76d2a 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -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(); } diff --git a/src/hotspot/share/cds/finalImageRecipes.cpp b/src/hotspot/share/cds/finalImageRecipes.cpp new file mode 100644 index 00000000000..55855679a1c --- /dev/null +++ b/src/hotspot/share/cds/finalImageRecipes.cpp @@ -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* 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 tmp_indy_klasses; + GrowableArray*> 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 indices; + + if (ik->constants()->cache() != nullptr) { + Array* 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* cp_indices = _indy_cp_indices->at(i); + GrowableArray 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); +} diff --git a/src/hotspot/share/cds/finalImageRecipes.hpp b/src/hotspot/share/cds/finalImageRecipes.hpp new file mode 100644 index 00000000000..f07d9787af9 --- /dev/null +++ b/src/hotspot/share/cds/finalImageRecipes.hpp @@ -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 class GrowableArray; +template 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* _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* _indy_klasses; + Array*>* _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 diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 5aa456ea438..c95a358de5f 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -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, diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index 11250f1a35c..ba2fe5be775 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -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) { diff --git a/src/hotspot/share/cds/lambdaFormInvokers.cpp b/src/hotspot/share/cds/lambdaFormInvokers.cpp index ee3e9cff782..fc1917086f3 100644 --- a/src/hotspot/share/cds/lambdaFormInvokers.cpp +++ b/src/hotspot/share/cds/lambdaFormInvokers.cpp @@ -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; + } } diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index b95d524cb1d..2e5ebff456e 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -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 diff --git a/src/hotspot/share/cds/runTimeClassInfo.hpp b/src/hotspot/share/cds/runTimeClassInfo.hpp index ca60e11736d..8ad2efcbccb 100644 --- a/src/hotspot/share/cds/runTimeClassInfo.hpp +++ b/src/hotspot/share/cds/runTimeClassInfo.hpp @@ -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; } diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index 55363d7f41f..18f2abfd5f4 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -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; diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 68f9556099f..811ba12a1e9 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -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 diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index b8e24bb2caa..2f7887c2d46 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -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()) { diff --git a/src/hotspot/share/classfile/systemDictionaryShared.hpp b/src/hotspot/share/classfile/systemDictionaryShared.hpp index 41e3d9c9716..7286bdad3fd 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp @@ -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() { diff --git a/src/hotspot/share/include/cds.h b/src/hotspot/share/include/cds.h index 16d9d74e398..2a80dd68abd 100644 --- a/src/hotspot/share/include/cds.h +++ b/src/hotspot/share/include/cds.h @@ -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 diff --git a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp index c1fc0bf669b..99d1d027db0 100644 --- a/src/hotspot/share/memory/metaspace.cpp +++ b/src/hotspot/share/memory/metaspace.cpp @@ -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(); } diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 8aba2bb2ba3..ee53e43c327 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -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() { diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 9e3f73596e2..fb1b84b826a 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -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(); } diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 420d6ab39d9..ab05cbeb891 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -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; } } diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index d6dda04e053..b2c24169505 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -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. diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 006f1be112a..32859bf2718 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -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); } diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index f58257da2ef..c71f5e2dfc9 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -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 \ diff --git a/test/hotspot/jtreg/runtime/cds/ArchiveDoesNotExist.java b/test/hotspot/jtreg/runtime/cds/ArchiveDoesNotExist.java index d8cf5816a1c..57e9269d8d1 100644 --- a/test/hotspot/jtreg/runtime/cds/ArchiveDoesNotExist.java +++ b/test/hotspot/jtreg/runtime/cds/ArchiveDoesNotExist.java @@ -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 diff --git a/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java index 98ff155abfa..aeb0476346d 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java @@ -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++; } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTLoaderConstraintsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTLoaderConstraintsTest.java new file mode 100644 index 00000000000..63be2038907 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTLoaderConstraintsTest.java @@ -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"); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BootClass.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BootClass.java new file mode 100644 index 00000000000..40c22772c86 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BootClass.java @@ -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); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java index c4648bd2eb3..9db886e20a5 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java @@ -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"); + } + } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBench.java b/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBench.java index a95119f1a47..332f2fc909e 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBench.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBench.java @@ -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; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/ArchiveConsistency.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/ArchiveConsistency.java index ea95a2c8711..ef0df473a98 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/ArchiveConsistency.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/ArchiveConsistency.java @@ -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 + ")"); } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java index 493d5fa5665..c8a04027a3d 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java @@ -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:"); }); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchiveUpgrade.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchiveUpgrade.java index 88697496ac6..852fa1823a1 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchiveUpgrade.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchiveUpgrade.java @@ -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) { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/dumpingWithAgent/DumpingWithJavaAgent.java b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/dumpingWithAgent/DumpingWithJavaAgent.java index 7d68fdf0fc8..54cb3fb23fb 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/dumpingWithAgent/DumpingWithJavaAgent.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/dumpingWithAgent/DumpingWithJavaAgent.java @@ -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"; diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java index 3a2fc1e1f69..7b4ad6eb5c4 100644 --- a/test/lib/jdk/test/lib/cds/CDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -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=.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(); + } }