8373392: Replace CDS object subgraphs with @AOTSafeClassInitializer

Reviewed-by: liach, heidinga
This commit is contained in:
Ioi Lam 2025-12-17 22:16:38 +00:00
parent b3fab41460
commit 232b41b222
30 changed files with 456 additions and 236 deletions

View File

@ -145,7 +145,7 @@ void AOTArtifactFinder::find_artifacts() {
#if INCLUDE_CDS_JAVA_HEAP
// Keep scanning until we discover no more class that need to be AOT-initialized.
if (CDSConfig::is_initing_classes_at_dump_time()) {
if (CDSConfig::is_dumping_aot_linked_classes()) {
while (_pending_aot_inited_classes->length() > 0) {
InstanceKlass* ik = _pending_aot_inited_classes->pop();
HeapShared::copy_and_rescan_aot_inited_mirror(ik);
@ -188,7 +188,7 @@ void AOTArtifactFinder::end_scanning_for_oops() {
}
void AOTArtifactFinder::add_aot_inited_class(InstanceKlass* ik) {
if (CDSConfig::is_initing_classes_at_dump_time()) {
if (CDSConfig::is_dumping_aot_linked_classes()) {
if (RegeneratedClasses::is_regenerated_object(ik)) {
precond(RegeneratedClasses::get_original_object(ik)->is_initialized());
} else {
@ -258,7 +258,7 @@ void AOTArtifactFinder::add_cached_instance_class(InstanceKlass* ik) {
return;
}
scan_oops_in_instance_class(ik);
if (ik->is_hidden() && CDSConfig::is_initing_classes_at_dump_time()) {
if (ik->is_hidden() && CDSConfig::is_dumping_aot_linked_classes()) {
bool succeed = AOTClassLinker::try_add_candidate(ik);
guarantee(succeed, "All cached hidden classes must be aot-linkable");
add_aot_inited_class(ik);

View File

@ -40,7 +40,7 @@ DEBUG_ONLY(InstanceKlass* _aot_init_class = nullptr;)
bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
assert(!ArchiveBuilder::is_active() || !ArchiveBuilder::current()->is_in_buffer_space(ik), "must be source klass");
if (!CDSConfig::is_initing_classes_at_dump_time()) {
if (!CDSConfig::is_dumping_aot_linked_classes()) {
return false;
}
@ -64,7 +64,7 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
// Automatic selection for aot-inited classes
// ==========================================
//
// When CDSConfig::is_initing_classes_at_dump_time is enabled,
// When CDSConfig::is_dumping_aot_linked_classes is enabled,
// AOTArtifactFinder::find_artifacts() finds the classes of all
// heap objects that are reachable from HeapShared::_run_time_special_subgraph,
// and mark these classes as aot-inited. This preserves the initialized
@ -310,7 +310,7 @@ void AOTClassInitializer::init_test_class(TRAPS) {
//
// -XX:AOTInitTestClass is NOT a general mechanism for including user-defined objects into
// the AOT cache. Therefore, this option is NOT available in product JVM.
if (AOTInitTestClass != nullptr && CDSConfig::is_initing_classes_at_dump_time()) {
if (AOTInitTestClass != nullptr && CDSConfig::is_dumping_aot_linked_classes()) {
log_info(aot)("Debug build only: force initialization of AOTInitTestClass %s", AOTInitTestClass);
TempNewSymbol class_name = SymbolTable::new_symbol(AOTInitTestClass);
Handle app_loader(THREAD, SystemDictionary::java_system_loader());

View File

@ -1141,7 +1141,7 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS
AOTReferenceObjSupport::initialize(CHECK);
AOTReferenceObjSupport::stabilize_cached_reference_objects(CHECK);
if (CDSConfig::is_initing_classes_at_dump_time()) {
if (CDSConfig::is_dumping_aot_linked_classes()) {
// java.lang.Class::reflectionFactory cannot be archived yet. We set this field
// to null, and it will be initialized again at runtime.
log_debug(aot)("Resetting Class::reflectionFactory");

View File

@ -1026,23 +1026,19 @@ void CDSConfig::set_has_aot_linked_classes(bool has_aot_linked_classes) {
_has_aot_linked_classes |= has_aot_linked_classes;
}
bool CDSConfig::is_initing_classes_at_dump_time() {
return is_dumping_heap() && is_dumping_aot_linked_classes();
}
bool CDSConfig::is_dumping_invokedynamic() {
// Requires is_dumping_aot_linked_classes(). Otherwise the classes of some archived heap
// objects used by the archive indy callsites may be replaced at runtime.
return AOTInvokeDynamicLinking && is_dumping_aot_linked_classes() && is_dumping_heap();
}
// When we are dumping aot-linked classes and we are able to write archived heap objects, we automatically
// enable the archiving of MethodHandles. This will in turn enable the archiving of MethodTypes and hidden
// When we are dumping aot-linked classes, we automatically enable the archiving of MethodHandles.
// This will in turn enable the archiving of MethodTypes and hidden
// classes that are used in the implementation of MethodHandles.
// Archived MethodHandles are required for higher-level optimizations such as AOT resolution of invokedynamic
// and dynamic proxies.
bool CDSConfig::is_dumping_method_handles() {
return is_initing_classes_at_dump_time();
return is_dumping_aot_linked_classes();
}
#endif // INCLUDE_CDS_JAVA_HEAP

View File

@ -187,7 +187,6 @@ public:
static void disable_heap_dumping() { CDS_ONLY(_disable_heap_dumping = true); }
static bool is_dumping_heap() NOT_CDS_JAVA_HEAP_RETURN_(false);
static bool is_loading_heap() NOT_CDS_JAVA_HEAP_RETURN_(false);
static bool is_initing_classes_at_dump_time() NOT_CDS_JAVA_HEAP_RETURN_(false);
static bool is_dumping_invokedynamic() NOT_CDS_JAVA_HEAP_RETURN_(false);
static bool is_dumping_method_handles() NOT_CDS_JAVA_HEAP_RETURN_(false);

View File

@ -40,7 +40,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) {
}
// !!! This is legacy support for enum classes before JEP 483. This file is not used when
// !!! CDSConfig::is_initing_classes_at_dump_time()==true.
// !!! CDSConfig::is_dumping_aot_linked_classes()==true.
//
// Java Enum classes have synthetic <clinit> methods that look like this
// enum MyEnum {FOO, BAR}
@ -63,7 +63,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) {
void CDSEnumKlass::handle_enum_obj(int level,
KlassSubGraphInfo* subgraph_info,
oop orig_obj) {
assert(!CDSConfig::is_initing_classes_at_dump_time(), "only for legacy support of enums");
assert(!CDSConfig::is_dumping_aot_linked_classes(), "only for legacy support of enums");
assert(level > 1, "must never be called at the first (outermost) level");
assert(is_enum_obj(orig_obj), "must be");

View File

@ -35,7 +35,7 @@ class JavaFieldStream;
class KlassSubGraphInfo;
// This is legacy support for enum classes before JEP 483. This code is not needed when
// CDSConfig::is_initing_classes_at_dump_time()==true.
// CDSConfig::is_dumping_aot_linked_classes()==true.
class CDSEnumKlass: AllStatic {
public:
static bool is_enum_obj(oop orig_obj);

View File

@ -156,7 +156,7 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0)
# undef ADD_EXCL
if (CDSConfig::is_initing_classes_at_dump_time()) {
if (CDSConfig::is_dumping_aot_linked_classes()) {
add_shared_secret_accessors();
}
ClassLoaderDataGraph::classes_do(this);

View File

@ -206,6 +206,8 @@ void FinalImageRecipes::load_all_classes(TRAPS) {
if (ik->has_aot_safe_initializer() && (flags & WAS_INITED) != 0) {
assert(ik->class_loader() == nullptr, "supported only for boot classes for now");
ResourceMark rm(THREAD);
log_info(aot, init)("Initializing %s", ik->external_name());
ik->initialize(CHECK);
}
}

View File

@ -209,8 +209,14 @@ static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], Instan
}
bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) {
return is_subgraph_root_class_of(archive_subgraph_entry_fields, ik) ||
is_subgraph_root_class_of(fmg_archive_subgraph_entry_fields, ik);
assert(CDSConfig::is_dumping_heap(), "dump-time only");
if (!CDSConfig::is_dumping_aot_linked_classes()) {
// Legacy CDS archive support (to be deprecated)
return is_subgraph_root_class_of(archive_subgraph_entry_fields, ik) ||
is_subgraph_root_class_of(fmg_archive_subgraph_entry_fields, ik);
} else {
return false;
}
}
oop HeapShared::CachedOopInfo::orig_referrer() const {
@ -934,12 +940,16 @@ void HeapShared::scan_java_class(Klass* orig_k) {
void HeapShared::archive_subgraphs() {
assert(CDSConfig::is_dumping_heap(), "must be");
archive_object_subgraphs(archive_subgraph_entry_fields,
false /* is_full_module_graph */);
if (!CDSConfig::is_dumping_aot_linked_classes()) {
archive_object_subgraphs(archive_subgraph_entry_fields,
false /* is_full_module_graph */);
if (CDSConfig::is_dumping_full_module_graph()) {
archive_object_subgraphs(fmg_archive_subgraph_entry_fields,
true /* is_full_module_graph */);
}
}
if (CDSConfig::is_dumping_full_module_graph()) {
archive_object_subgraphs(fmg_archive_subgraph_entry_fields,
true /* is_full_module_graph */);
Modules::verify_archived_modules();
}
}
@ -1295,8 +1305,10 @@ void HeapShared::resolve_classes(JavaThread* current) {
if (!is_archived_heap_in_use()) {
return; // nothing to do
}
resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields);
resolve_classes_for_subgraphs(current, fmg_archive_subgraph_entry_fields);
if (!CDSConfig::is_using_aot_linked_classes()) {
resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields);
resolve_classes_for_subgraphs(current, fmg_archive_subgraph_entry_fields);
}
}
void HeapShared::resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]) {
@ -1734,13 +1746,13 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap
}
}
if (CDSConfig::is_initing_classes_at_dump_time()) {
if (CDSConfig::is_dumping_aot_linked_classes()) {
if (java_lang_Class::is_instance(orig_obj)) {
orig_obj = scratch_java_mirror(orig_obj);
assert(orig_obj != nullptr, "must be archived");
}
} else if (java_lang_Class::is_instance(orig_obj) && subgraph_info != _dump_time_special_subgraph) {
// Without CDSConfig::is_initing_classes_at_dump_time(), we only allow archived objects to
// Without CDSConfig::is_dumping_aot_linked_classes(), we only allow archived objects to
// point to the mirrors of (1) j.l.Object, (2) primitive classes, and (3) box classes. These are initialized
// very early by HeapShared::init_box_classes().
if (orig_obj == vmClasses::Object_klass()->java_mirror()
@ -1808,9 +1820,9 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap
orig_obj->oop_iterate(&pusher);
}
if (CDSConfig::is_initing_classes_at_dump_time()) {
// The classes of all archived enum instances have been marked as aot-init,
// so there's nothing else to be done in the production run.
if (CDSConfig::is_dumping_aot_linked_classes()) {
// The enum klasses are archived with aot-initialized mirror.
// See AOTClassInitializer::can_archive_initialized_mirror().
} else {
// This is legacy support for enum classes before JEP 483 -- we cannot rerun
// the enum's <clinit> in the production run, so special handling is needed.
@ -1949,7 +1961,7 @@ void HeapShared::verify_reachable_objects_from(oop obj) {
#endif
void HeapShared::check_special_subgraph_classes() {
if (CDSConfig::is_initing_classes_at_dump_time()) {
if (CDSConfig::is_dumping_aot_linked_classes()) {
// We can have aot-initialized classes (such as Enums) that can reference objects
// of arbitrary types. Currently, we trust the JEP 483 implementation to only
// aot-initialize classes that are "safe".
@ -2136,9 +2148,11 @@ void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
void HeapShared::init_subgraph_entry_fields(TRAPS) {
assert(CDSConfig::is_dumping_heap(), "must be");
_dump_time_subgraph_info_table = new (mtClass)DumpTimeKlassSubGraphInfoTable();
init_subgraph_entry_fields(archive_subgraph_entry_fields, CHECK);
if (CDSConfig::is_dumping_full_module_graph()) {
init_subgraph_entry_fields(fmg_archive_subgraph_entry_fields, CHECK);
if (!CDSConfig::is_dumping_aot_linked_classes()) {
init_subgraph_entry_fields(archive_subgraph_entry_fields, CHECK);
if (CDSConfig::is_dumping_full_module_graph()) {
init_subgraph_entry_fields(fmg_archive_subgraph_entry_fields, CHECK);
}
}
}

View File

@ -26,6 +26,7 @@
package java.lang;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
@ -103,6 +104,7 @@ public final class Byte extends Number implements Comparable<Byte>, Constable {
return Optional.of(DynamicConstantDesc.ofNamed(BSM_EXPLICIT_CAST, DEFAULT_NAME, CD_byte, intValue()));
}
@AOTSafeClassInitializer
private static final class ByteCache {
private ByteCache() {}

View File

@ -26,6 +26,7 @@
package java.lang;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
@ -9379,6 +9380,7 @@ class Character implements java.io.Serializable, Comparable<Character>, Constabl
this.value = value;
}
@AOTSafeClassInitializer
private static final class CharacterCache {
private CharacterCache(){}

View File

@ -28,6 +28,8 @@ package java.lang;
import jdk.internal.misc.CDS;
import jdk.internal.misc.VM;
import jdk.internal.util.DecimalDigits;
import jdk.internal.vm.annotation.AOTRuntimeSetup;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
@ -891,15 +893,20 @@ public final class Integer extends Number
* with new Integer object(s) after initialization.
*/
@AOTSafeClassInitializer
private static final class IntegerCache {
static final int low = -128;
static final int high;
@Stable static int high;
@Stable
static final Integer[] cache;
@Stable static Integer[] cache;
static Integer[] archivedCache;
static {
runtimeSetup();
}
@AOTRuntimeSetup
private static void runtimeSetup() {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
@ -915,34 +922,50 @@ public final class Integer extends Number
}
high = h;
// Load IntegerCache.archivedCache from archive, if possible
CDS.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
// If archive has Integer cache, we must use all instances from it.
// Otherwise, the identity checks between archived Integers and
// runtime-cached Integers would fail.
int archivedSize = (archivedCache == null) ? 0 : archivedCache.length;
for (int i = 0; i < archivedSize; i++) {
c[i] = archivedCache[i];
assert j == archivedCache[i];
j++;
}
// Fill the rest of the cache.
for (int i = archivedSize; i < size; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
Integer[] precomputed = null;
if (cache != null) {
// IntegerCache has been AOT-initialized.
precomputed = cache;
} else {
// Legacy CDS archive support (to be deprecated):
// Load IntegerCache.archivedCache from archive, if possible
CDS.initializeFromArchive(IntegerCache.class);
precomputed = archivedCache;
}
cache = archivedCache;
cache = loadOrInitializeCache(precomputed);
archivedCache = cache; // Legacy CDS archive support (to be deprecated)
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private static Integer[] loadOrInitializeCache(Integer[] precomputed) {
int size = (high - low) + 1;
// Use the precomputed cache if it exists and is large enough
if (precomputed != null && size <= precomputed.length) {
return precomputed;
}
Integer[] c = new Integer[size];
int j = low;
// If we loading a precomputed cache (from AOT cache or CDS archive),
// we must use all instances from it.
// Otherwise, the Integers from the AOT cache (or CDS archive) will not
// have the same object identity as items in IntegerCache.cache[].
int precomputedSize = (precomputed == null) ? 0 : precomputed.length;
for (int i = 0; i < precomputedSize; i++) {
c[i] = precomputed[i];
assert j == precomputed[i];
j++;
}
// Fill the rest of the cache.
for (int i = precomputedSize; i < size; i++) {
c[i] = new Integer(j++);
}
return c;
}
private IntegerCache() {}
}

View File

@ -35,6 +35,7 @@ import java.util.Optional;
import jdk.internal.misc.CDS;
import jdk.internal.util.DecimalDigits;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
@ -911,6 +912,7 @@ public final class Long extends Number
return Long.valueOf(parseLong(s, 10));
}
@AOTSafeClassInitializer
private static final class LongCache {
private LongCache() {}

View File

@ -69,6 +69,7 @@ import jdk.internal.module.ServicesCatalog;
import jdk.internal.module.Resources;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.Stable;
/**
@ -391,6 +392,7 @@ public final class Module implements AnnotatedElement {
private static final Module EVERYONE_MODULE;
private static final Set<Module> EVERYONE_SET;
@AOTSafeClassInitializer
private static class ArchivedData {
private static ArchivedData archivedData;
private final Module allUnnamedModule;

View File

@ -53,6 +53,7 @@ import jdk.internal.module.ServicesCatalog;
import jdk.internal.misc.CDS;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.Stable;
/**
@ -145,6 +146,7 @@ import jdk.internal.vm.annotation.Stable;
* @see Module#getLayer()
*/
@AOTSafeClassInitializer
public final class ModuleLayer {
// the empty layer (may be initialized from the CDS archive)

View File

@ -26,6 +26,7 @@
package java.lang;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
@ -230,6 +231,7 @@ public final class Short extends Number implements Comparable<Short>, Constable
return Optional.of(DynamicConstantDesc.ofNamed(BSM_EXPLICIT_CAST, DEFAULT_NAME, CD_short, intValue()));
}
@AOTSafeClassInitializer
private static final class ShortCache {
private ShortCache() {}

View File

@ -44,6 +44,7 @@ import java.util.stream.Stream;
import jdk.internal.misc.CDS;
import jdk.internal.module.ModuleReferenceImpl;
import jdk.internal.module.ModuleTarget;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.Stable;
/**
@ -155,6 +156,7 @@ import jdk.internal.vm.annotation.Stable;
* @since 9
* @see java.lang.ModuleLayer
*/
@AOTSafeClassInitializer
public final class Configuration {
// @see Configuration#empty()

View File

@ -42,6 +42,9 @@ import java.util.function.UnaryOperator;
import jdk.internal.access.JavaUtilCollectionAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTRuntimeSetup;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
/**
@ -52,6 +55,7 @@ import jdk.internal.vm.annotation.Stable;
* classes use a serial proxy and thus have no need to declare serialVersionUID.
*/
@SuppressWarnings("serial")
@AOTSafeClassInitializer
class ImmutableCollections {
/**
* A "salt" value used for randomizing iteration order. This is initialized once
@ -59,14 +63,20 @@ class ImmutableCollections {
* it needs to vary sufficiently from one run to the next so that iteration order
* will vary between JVM runs.
*/
private static final long SALT32L;
@Stable private static long SALT32L;
/**
* For set and map iteration, we will iterate in "reverse" stochastically,
* decided at bootstrap time.
*/
private static final boolean REVERSE;
@Stable private static boolean REVERSE;
static {
runtimeSetup();
}
@AOTRuntimeSetup
private static void runtimeSetup() {
// to generate a reasonably random and well-mixed SALT, use an arbitrary
// value (a slice of pi), multiply with a random seed, then pick
// the mid 32-bits from the product. By picking a SALT value in the
@ -102,6 +112,7 @@ class ImmutableCollections {
static final MapN<?,?> EMPTY_MAP;
static {
// Legacy CDS archive support (to be deprecated)
CDS.initializeFromArchive(ImmutableCollections.class);
if (archivedObjects == null) {
EMPTY = new Object();

View File

@ -36,6 +36,7 @@ import java.util.Objects;
import java.util.Set;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.Stable;
import sun.nio.cs.UTF_8;
@ -60,6 +61,7 @@ import sun.util.logging.PlatformLogger;
* @see Manifest
* @since 1.2
*/
@AOTSafeClassInitializer
public class Attributes implements Map<Object,Object>, Cloneable {
/**
* The attribute name-value mappings.
@ -450,6 +452,7 @@ public class Attributes implements Map<Object,Object>, Cloneable {
*
* @spec jar/jar.html JAR File Specification
*/
@AOTSafeClassInitializer
public static class Name {
private final String name;
private final int hashCode;
@ -669,6 +672,7 @@ public class Attributes implements Map<Object,Object>, Cloneable {
static {
// Legacy CDS archive support (to be deprecated)
CDS.initializeFromArchive(Attributes.Name.class);
if (KNOWN_NAMES == null) {

View File

@ -27,11 +27,13 @@ package jdk.internal.loader;
import java.util.Map;
import jdk.internal.misc.CDS;
import jdk.internal.module.ServicesCatalog;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
/**
* Used to archive the built-in class loaders, their services catalogs, and the
* package-to-module map used by the built-in class loaders.
*/
@AOTSafeClassInitializer
class ArchivedClassLoaders {
private static ArchivedClassLoaders archivedClassLoaders;

View File

@ -26,6 +26,7 @@
package jdk.internal.math;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.Stable;
import java.util.Arrays;
@ -33,6 +34,7 @@ import java.util.Arrays;
/**
* A simple big integer class specifically for floating point base conversion.
*/
@AOTSafeClassInitializer
final class FDBigInteger {
@Stable
@ -53,6 +55,7 @@ final class FDBigInteger {
// Initialize FDBigInteger cache of powers of 5.
static {
// Legacy CDS archive support (to be deprecated)
CDS.initializeFromArchive(FDBigInteger.class);
Object[] caches = archivedCaches;
if (caches == null) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 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
@ -25,10 +25,12 @@
package jdk.internal.module;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
/**
* Used by ModuleBootstrap for archiving the boot layer.
*/
@AOTSafeClassInitializer
class ArchivedBootLayer {
private static ArchivedBootLayer archivedBootLayer;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -30,11 +30,13 @@ import java.util.function.Function;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import jdk.internal.misc.CDS;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
/**
* Used by ModuleBootstrap for archiving the configuration for the boot layer,
* and the system module finder.
*/
@AOTSafeClassInitializer
class ArchivedModuleGraph {
private static ArchivedModuleGraph archivedModuleGraph;
@ -126,6 +128,7 @@ class ArchivedModuleGraph {
}
static {
// Legacy CDS archive support (to be deprecated)
CDS.initializeFromArchive(ArchivedModuleGraph.class);
}
}

View File

@ -34,11 +34,14 @@ package sun.util.locale;
import jdk.internal.misc.CDS;
import jdk.internal.util.ReferencedKeySet;
import jdk.internal.vm.annotation.AOTRuntimeSetup;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.Stable;
import java.util.StringJoiner;
import java.util.function.Supplier;
@AOTSafeClassInitializer
public final class BaseLocale {
public static @Stable BaseLocale[] constantBaseLocales;
@ -63,6 +66,7 @@ public final class BaseLocale {
CANADA_FRENCH = 18,
NUM_CONSTANTS = 19;
static {
// Legacy CDS archive support (to be deprecated)
CDS.initializeFromArchive(BaseLocale.class);
BaseLocale[] baseLocales = constantBaseLocales;
if (baseLocales == null) {
@ -91,13 +95,21 @@ public final class BaseLocale {
}
// Interned BaseLocale cache
private static final LazyConstant<ReferencedKeySet<BaseLocale>> CACHE =
@Stable private static LazyConstant<ReferencedKeySet<BaseLocale>> CACHE;
static {
runtimeSetup();
}
@AOTRuntimeSetup
private static void runtimeSetup() {
CACHE =
LazyConstant.of(new Supplier<>() {
@Override
public ReferencedKeySet<BaseLocale> get() {
return ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier());
}
});
}
public static final String SEP = "_";

View File

@ -522,6 +522,7 @@ hotspot_aot_classlinking = \
-runtime/cds/appcds/aotFlags \
-runtime/cds/appcds/aotProfile \
-runtime/cds/appcds/BadBSM.java \
-runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java \
-runtime/cds/appcds/cacheObject/ArchivedIntegerCacheTest.java \
-runtime/cds/appcds/cacheObject/ArchivedModuleCompareTest.java \
-runtime/cds/appcds/CDSandJFR.java \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2021, 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
@ -31,6 +31,8 @@
* java.management
*/
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.process.OutputAnalyzer;
@ -43,8 +45,44 @@ public class SharedSymbolTableBucketSize {
+ Integer.valueOf(bucket_size));
CDSTestUtils.checkMappingFailure(output);
String regex = "Average bucket size : ([0-9]+\\.[0-9]+).*";
String s = output.firstMatch(regex, 1);
/* [1] There may be other table stats that precede the symbol tabble.
Skip all thse until we find this:
[0.677s][info][aot,hashtables] Shared symbol table stats -------- base: 0x0000000800000000
[0.677s][info][aot,hashtables] Number of entries : 46244
[0.677s][info][aot,hashtables] Total bytes used : 393792
[0.677s][info][aot,hashtables] Average bytes per entry : 8.516
[0.677s][info][aot,hashtables] Average bucket size : 7.734
[0.677s][info][aot,hashtables] Variance of bucket size : 7.513
[0.677s][info][aot,hashtables] Std. dev. of bucket size: 2.741
[0.677s][info][aot,hashtables] Maximum bucket size : 20
[0.677s][info][aot,hashtables] Empty buckets : 2
[0.677s][info][aot,hashtables] Value_Only buckets : 24
[0.677s][info][aot,hashtables] Other buckets : 5953
....
*/
Pattern pattern0 = Pattern.compile("Shared symbol table stats.*", Pattern.DOTALL);
Matcher matcher0 = pattern0.matcher(output.getStdout());
String stat = null;
if (matcher0.find()) {
stat = matcher0.group(0);
}
if (stat == null) {
throw new Exception("FAILED: pattern \"" + pattern0 + "\" not found");
}
/* (2) The first "Average bucket size" line in the remaining output is for the
shared symbol table */
Pattern pattern = Pattern.compile("Average bucket size *: *([0-9]+\\.[0-9]+).*", Pattern.MULTILINE);
Matcher matcher = pattern.matcher(stat);
String s = null;
if (matcher.find()) {
s = matcher.group(1);
}
if (s == null) {
throw new Exception("FAILED: pattern \"" + pattern + "\" not found");
}
Float f = Float.parseFloat(s);
int size = Math.round(f);
if (size != bucket_size) {

View File

@ -83,17 +83,6 @@ public class AOTLoggingTag {
out.shouldContain("[aot] Opened AOT cache hello.aot");
out.shouldHaveExitValue(0);
//----------------------------------------------------------------------
printTestCase("All old -Xlog:cds+heap logs have been changed to -Xlog:aot+heap should alias to -Xlog:cds+heap");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:AOTCache=" + aotCacheFile,
"-Xlog:aot+heap",
"-cp", appJar, helloClass);
out = CDSTestUtils.executeAndLog(pb, "prod");
out.shouldNotContain("No tag set matches selection: aot+heap");
out.shouldContain("[aot,heap] resolve subgraph java.lang.Integer$IntegerCache");
out.shouldHaveExitValue(0);
//----------------------------------------------------------------------
printTestCase("Production Run: errors should be printed with [aot] decoration");
pb = ProcessTools.createLimitedTestJavaProcessBuilder(

View File

@ -0,0 +1,261 @@
/*
* 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 AOT cache should preserve heap object identity when required by JLS. For example, Enums and Integers.
* @requires vm.cds
* @requires vm.cds.supports.aot.class.linking
* @requires vm.debug
* @library /test/lib
* @build HeapObjectIdentity
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar dummy.jar
* Dummy
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar
* HeapObjectIdentityApp
* MyAOTInitedClass
* MyAOTInitedClass$MyEnum
* MyAOTInitedClass$Wrapper
* @run driver HeapObjectIdentity AOT --two-step-training
*/
import jdk.test.lib.cds.CDSAppTester;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.helpers.ClassFileInstaller;
public class HeapObjectIdentity {
static final String appJar = ClassFileInstaller.getJarPath("dummy.jar");
static final String bootJar = ClassFileInstaller.getJarPath("boot.jar");
static final String mainClass = "HeapObjectIdentityApp"; // Loaded from boot.jar
public static void main(String[] args) throws Exception {
Tester t = new Tester();
t.run(args);
// Integer$IntegerCache should preserve the object identity of cached Integer objects,
// even when the cache size is different between assembly and production.
t.productionRun(new String[] {
"-XX:AutoBoxCacheMax=2048"
});
}
static class Tester extends CDSAppTester {
public Tester() {
super(mainClass);
}
@Override
public String classpath(RunMode runMode) {
return appJar;
}
@Override
public String[] vmArgs(RunMode runMode) {
String bootcp = "-Xbootclasspath/a:" + bootJar;
if (runMode == RunMode.ASSEMBLY) {
return new String[] {
"-Xlog:aot+class=debug",
"-XX:AOTInitTestClass=MyAOTInitedClass",
bootcp
};
} else {
return new String[] {bootcp};
}
}
@Override
public String[] appCommandLine(RunMode runMode) {
return new String[] {
mainClass,
runMode.toString(),
};
}
@Override
public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
if (runMode == RunMode.ASSEMBLY) {
out.shouldContain("MyAOTInitedClass aot-linked inited");
}
}
}
}
class HeapObjectIdentityApp {
public static void main(String... args) {
MyAOTInitedClass.test();
}
}
// This class is loaded by the boot loader, as -XX:AOTInitTestClass is not too friendly
// with classes by other loaders.
class MyAOTInitedClass {
static Object[] archivedObjects;
static {
if (archivedObjects == null) {
archivedObjects = new Object[14];
archivedObjects[0] = Wrapper.BOOLEAN;
archivedObjects[1] = Wrapper.INT.zero();
archivedObjects[2] = Wrapper.DOUBLE.zero();
archivedObjects[3] = MyEnum.DUMMY1;
archivedObjects[4] = Boolean.class;
archivedObjects[5] = Byte.class;
archivedObjects[6] = Character.class;
archivedObjects[7] = Short.class;
archivedObjects[8] = Integer.class;
archivedObjects[9] = Long.class;
archivedObjects[10] = Float.class;
archivedObjects[11] = Double.class;
archivedObjects[12] = Void.class;
archivedObjects[13] = Integer.valueOf(1);
} else {
System.out.println("Initialized from CDS");
}
}
public static void test() {
if (archivedObjects[0] != Wrapper.BOOLEAN) {
throw new RuntimeException("Huh 0");
}
if (archivedObjects[1] != Wrapper.INT.zero()) {
throw new RuntimeException("Huh 1");
}
if (archivedObjects[2] != Wrapper.DOUBLE.zero()) {
throw new RuntimeException("Huh 2");
}
if (archivedObjects[3] != MyEnum.DUMMY1) {
throw new RuntimeException("Huh 3");
}
if (MyEnum.BOOLEAN != true) {
throw new RuntimeException("Huh 10.1");
}
if (MyEnum.BYTE != -128) {
throw new RuntimeException("Huh 10.2");
}
if (MyEnum.CHAR != 'c') {
throw new RuntimeException("Huh 10.3");
}
if (MyEnum.SHORT != -12345) {
throw new RuntimeException("Huh 10.4");
}
if (MyEnum.INT != -123456) {
throw new RuntimeException("Huh 10.5");
}
if (MyEnum.LONG != 0x1234567890L) {
throw new RuntimeException("Huh 10.6");
}
if (MyEnum.LONG2 != -0x1234567890L) {
throw new RuntimeException("Huh 10.7");
}
if (MyEnum.FLOAT != 567891.0f) {
throw new RuntimeException("Huh 10.8");
}
if (MyEnum.DOUBLE != 12345678905678.890) {
throw new RuntimeException("Huh 10.9");
}
checkClass(4, Boolean.class);
checkClass(5, Byte.class);
checkClass(6, Character.class);
checkClass(7, Short.class);
checkClass(8, Integer.class);
checkClass(9, Long.class);
checkClass(10, Float.class);
checkClass(11, Double.class);
checkClass(12, Void.class);
if (archivedObjects[13] != Integer.valueOf(1)) {
throw new RuntimeException("Integer cache identity test failed");
}
System.out.println("Success!");
}
static void checkClass(int index, Class c) {
if (archivedObjects[index] != c) {
throw new RuntimeException("archivedObjects[" + index + "] should be " + c);
}
}
// Simplified version of sun.invoke.util.Wrapper
public enum Wrapper {
// wrapperType simple primitiveType simple char emptyArray
BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0]),
INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0]),
DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0])
;
public static final int COUNT = 10;
private static final Object DOUBLE_ZERO = (Double)(double)0;
private final Class<?> wrapperType;
private final Class<?> primitiveType;
private final char basicTypeChar;
private final String basicTypeString;
private final Object emptyArray;
Wrapper(Class<?> wtype,
String wtypeName,
Class<?> ptype,
String ptypeName,
char tchar,
Object emptyArray) {
this.wrapperType = wtype;
this.primitiveType = ptype;
this.basicTypeChar = tchar;
this.basicTypeString = String.valueOf(this.basicTypeChar);
this.emptyArray = emptyArray;
}
public Object zero() {
return switch (this) {
case BOOLEAN -> Boolean.FALSE;
case INT -> (Integer)0;
case DOUBLE -> DOUBLE_ZERO;
default -> null;
};
}
}
enum MyEnum {
DUMMY1,
DUMMY2;
static final boolean BOOLEAN = true;
static final byte BYTE = -128;
static final short SHORT = -12345;
static final char CHAR = 'c';
static final int INT = -123456;
static final long LONG = 0x1234567890L;
static final long LONG2 = -0x1234567890L;
static final float FLOAT = 567891.0f;
static final double DOUBLE = 12345678905678.890;
}
}
class Dummy {}

View File

@ -36,7 +36,7 @@
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar
* CDSTestClassA CDSTestClassA$XX CDSTestClassA$YY
* CDSTestClassB CDSTestClassC CDSTestClassD
* CDSTestClassE CDSTestClassF CDSTestClassG CDSTestClassG$MyEnum CDSTestClassG$Wrapper
* CDSTestClassE CDSTestClassF
* pkg.ClassInPackage
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar Hello
* @run driver ArchiveHeapTestClass
@ -58,7 +58,6 @@ public class ArchiveHeapTestClass {
static final String CDSTestClassD_name = CDSTestClassD.class.getName();
static final String CDSTestClassE_name = CDSTestClassE.class.getName();
static final String CDSTestClassF_name = CDSTestClassF.class.getName();
static final String CDSTestClassG_name = CDSTestClassG.class.getName();
static final String ClassInPackage_name = pkg.ClassInPackage.class.getName().replace('.', '/');
static final String ARCHIVE_TEST_FIELD_NAME = "archivedObjects";
@ -162,15 +161,6 @@ public class ArchiveHeapTestClass {
output = dumpBootAndHello(CDSTestClassF_name);
mustFail(output, "Class java.util.logging.Level not allowed in archive heap");
}
testCase("Complex enums");
output = dumpBootAndHello(CDSTestClassG_name, "-XX:+AOTClassLinking", "-Xlog:cds+class=debug");
mustSucceed(output);
TestCommon.run("-Xbootclasspath/a:" + bootJar, "-cp", appJar, "-Xlog:aot+heap,cds+init",
CDSTestClassG_name)
.assertNormalExit("init subgraph " + CDSTestClassG_name,
"Initialized from CDS");
}
}
@ -287,147 +277,3 @@ class CDSTestClassF {
archivedObjects[0] = java.util.logging.Level.OFF;
}
}
class CDSTestClassG {
static Object[] archivedObjects;
static {
if (archivedObjects == null) {
archivedObjects = new Object[13];
archivedObjects[0] = Wrapper.BOOLEAN;
archivedObjects[1] = Wrapper.INT.zero();
archivedObjects[2] = Wrapper.DOUBLE.zero();
archivedObjects[3] = MyEnum.DUMMY1;
archivedObjects[4] = Boolean.class;
archivedObjects[5] = Byte.class;
archivedObjects[6] = Character.class;
archivedObjects[7] = Short.class;
archivedObjects[8] = Integer.class;
archivedObjects[9] = Long.class;
archivedObjects[10] = Float.class;
archivedObjects[11] = Double.class;
archivedObjects[12] = Void.class;
} else {
System.out.println("Initialized from CDS");
}
}
public static void main(String args[]) {
if (archivedObjects[0] != Wrapper.BOOLEAN) {
throw new RuntimeException("Huh 0");
}
if (archivedObjects[1] != Wrapper.INT.zero()) {
throw new RuntimeException("Huh 1");
}
if (archivedObjects[2] != Wrapper.DOUBLE.zero()) {
throw new RuntimeException("Huh 2");
}
if (archivedObjects[3] != MyEnum.DUMMY1) {
throw new RuntimeException("Huh 3");
}
if (MyEnum.BOOLEAN != true) {
throw new RuntimeException("Huh 10.1");
}
if (MyEnum.BYTE != -128) {
throw new RuntimeException("Huh 10.2");
}
if (MyEnum.CHAR != 'c') {
throw new RuntimeException("Huh 10.3");
}
if (MyEnum.SHORT != -12345) {
throw new RuntimeException("Huh 10.4");
}
if (MyEnum.INT != -123456) {
throw new RuntimeException("Huh 10.5");
}
if (MyEnum.LONG != 0x1234567890L) {
throw new RuntimeException("Huh 10.6");
}
if (MyEnum.LONG2 != -0x1234567890L) {
throw new RuntimeException("Huh 10.7");
}
if (MyEnum.FLOAT != 567891.0f) {
throw new RuntimeException("Huh 10.8");
}
if (MyEnum.DOUBLE != 12345678905678.890) {
throw new RuntimeException("Huh 10.9");
}
checkClass(4, Boolean.class);
checkClass(5, Byte.class);
checkClass(6, Character.class);
checkClass(7, Short.class);
checkClass(8, Integer.class);
checkClass(9, Long.class);
checkClass(10, Float.class);
checkClass(11, Double.class);
checkClass(12, Void.class);
System.out.println("Success!");
}
static void checkClass(int index, Class c) {
if (archivedObjects[index] != c) {
throw new RuntimeException("archivedObjects[" + index + "] should be " + c);
}
}
// Simplified version of sun.invoke.util.Wrapper
public enum Wrapper {
// wrapperType simple primitiveType simple char emptyArray
BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0]),
INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0]),
DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0])
;
public static final int COUNT = 10;
private static final Object DOUBLE_ZERO = (Double)(double)0;
private final Class<?> wrapperType;
private final Class<?> primitiveType;
private final char basicTypeChar;
private final String basicTypeString;
private final Object emptyArray;
Wrapper(Class<?> wtype,
String wtypeName,
Class<?> ptype,
String ptypeName,
char tchar,
Object emptyArray) {
this.wrapperType = wtype;
this.primitiveType = ptype;
this.basicTypeChar = tchar;
this.basicTypeString = String.valueOf(this.basicTypeChar);
this.emptyArray = emptyArray;
}
public Object zero() {
return switch (this) {
case BOOLEAN -> Boolean.FALSE;
case INT -> (Integer)0;
case DOUBLE -> DOUBLE_ZERO;
default -> null;
};
}
}
enum MyEnum {
DUMMY1,
DUMMY2;
static final boolean BOOLEAN = true;
static final byte BYTE = -128;
static final short SHORT = -12345;
static final char CHAR = 'c';
static final int INT = -123456;
static final long LONG = 0x1234567890L;
static final long LONG2 = -0x1234567890L;
static final float FLOAT = 567891.0f;
static final double DOUBLE = 12345678905678.890;
}
}