Skip to content

Commit adfb120

Browse files
committed
8351748: Add class init barrier to AOT-cached Method/Var Handles
Reviewed-by: vlivanov, liach
1 parent ee1577b commit adfb120

File tree

14 files changed

+397
-22
lines changed

14 files changed

+397
-22
lines changed

src/hotspot/share/cds/aotClassInitializer.cpp

+45-2
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,16 @@
2626
#include "cds/archiveBuilder.hpp"
2727
#include "cds/cdsConfig.hpp"
2828
#include "cds/heapShared.hpp"
29+
#include "classfile/symbolTable.hpp"
30+
#include "classfile/systemDictionaryShared.hpp"
2931
#include "classfile/vmSymbols.hpp"
3032
#include "oops/instanceKlass.inline.hpp"
3133
#include "oops/symbol.hpp"
34+
#include "runtime/java.hpp"
3235
#include "runtime/javaCalls.hpp"
3336

37+
DEBUG_ONLY(InstanceKlass* _aot_init_class = nullptr;)
38+
3439
// Detector for class names we wish to handle specially.
3540
// It is either an exact string match or a string prefix match.
3641
class AOTClassInitializer::AllowedSpec {
@@ -93,12 +98,12 @@ bool AOTClassInitializer::is_allowed(AllowedSpec* specs, InstanceKlass* ik) {
9398

9499

95100
bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
96-
assert(!ArchiveBuilder::current()->is_in_buffer_space(ik), "must be source klass");
101+
assert(!ArchiveBuilder::is_active() || !ArchiveBuilder::current()->is_in_buffer_space(ik), "must be source klass");
97102
if (!CDSConfig::is_initing_classes_at_dump_time()) {
98103
return false;
99104
}
100105

101-
if (!ik->is_initialized()) {
106+
if (!ik->is_initialized() && !ik->is_being_initialized()) {
102107
return false;
103108
}
104109

@@ -293,9 +298,11 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
293298
{"java/lang/invoke/LambdaForm"},
294299
{"java/lang/invoke/LambdaForm$Holder"}, // UNSAFE.ensureClassInitialized()
295300
{"java/lang/invoke/LambdaForm$NamedFunction"},
301+
{"java/lang/invoke/LambdaMetafactory"},
296302
{"java/lang/invoke/MethodHandle"},
297303
{"java/lang/invoke/MethodHandles"},
298304
{"java/lang/invoke/SimpleMethodHandle"},
305+
{"java/lang/invoke/StringConcatFactory"},
299306
{"java/util/Collections"},
300307
{"java/util/stream/Collectors"},
301308
{"jdk/internal/constant/ConstantUtils"},
@@ -315,6 +322,12 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
315322
}
316323
}
317324

325+
#ifdef ASSERT
326+
if (ik == _aot_init_class) {
327+
return true;
328+
}
329+
#endif
330+
318331
return false;
319332
}
320333

@@ -345,3 +358,33 @@ void AOTClassInitializer::call_runtime_setup(JavaThread* current, InstanceKlass*
345358
}
346359
}
347360
}
361+
362+
#ifdef ASSERT
363+
void AOTClassInitializer::init_test_class(TRAPS) {
364+
// -XX:AOTInitTestClass is used in regression tests for adding additional AOT-initialized classes
365+
// and heap objects into the AOT cache. The tests must be carefully written to avoid including
366+
// any classes that cannot be AOT-initialized.
367+
//
368+
// -XX:AOTInitTestClass is NOT a general mechanism for including user-defined objects into
369+
// the AOT cache. Therefore, this option is NOT available in product JVM.
370+
if (AOTInitTestClass != nullptr && CDSConfig::is_initing_classes_at_dump_time()) {
371+
log_info(cds)("Debug build only: force initialization of AOTInitTestClass %s", AOTInitTestClass);
372+
TempNewSymbol class_name = SymbolTable::new_symbol(AOTInitTestClass);
373+
Handle app_loader(THREAD, SystemDictionary::java_system_loader());
374+
Klass* k = SystemDictionary::resolve_or_null(class_name, app_loader, CHECK);
375+
if (k == nullptr) {
376+
vm_exit_during_initialization("AOTInitTestClass not found", AOTInitTestClass);
377+
}
378+
if (!k->is_instance_klass()) {
379+
vm_exit_during_initialization("Invalid name for AOTInitTestClass", AOTInitTestClass);
380+
}
381+
382+
_aot_init_class = InstanceKlass::cast(k);
383+
_aot_init_class->initialize(CHECK);
384+
}
385+
}
386+
387+
bool AOTClassInitializer::has_test_class() {
388+
return _aot_init_class != nullptr;
389+
}
390+
#endif

src/hotspot/share/cds/aotClassInitializer.hpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -41,6 +41,10 @@ class AOTClassInitializer : AllStatic {
4141

4242
static bool is_runtime_setup_required(InstanceKlass* ik);
4343
static void call_runtime_setup(JavaThread* current, InstanceKlass* ik);
44+
45+
// Support for regression testing. Available in debug builds only.
46+
static void init_test_class(TRAPS) NOT_DEBUG_RETURN;
47+
static bool has_test_class() NOT_DEBUG({ return false; });
4448
};
4549

4650
#endif // SHARE_CDS_AOTCLASSINITIALIZER_HPP

src/hotspot/share/cds/cdsConfig.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ JavaThread* CDSConfig::_dumper_thread = nullptr;
6060
int CDSConfig::get_status() {
6161
assert(Universe::is_fully_initialized(), "status is finalized only after Universe is initialized");
6262
return (is_dumping_archive() ? IS_DUMPING_ARCHIVE : 0) |
63+
(is_dumping_method_handles() ? IS_DUMPING_METHOD_HANDLES : 0) |
6364
(is_dumping_static_archive() ? IS_DUMPING_STATIC_ARCHIVE : 0) |
6465
(is_logging_lambda_form_invokers() ? IS_LOGGING_LAMBDA_FORM_INVOKERS : 0) |
6566
(is_using_archive() ? IS_USING_ARCHIVE : 0);

src/hotspot/share/cds/cdsConfig.hpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@ class CDSConfig : public AllStatic {
6868
public:
6969
// Used by jdk.internal.misc.CDS.getCDSConfigStatus();
7070
static const int IS_DUMPING_ARCHIVE = 1 << 0;
71-
static const int IS_DUMPING_STATIC_ARCHIVE = 1 << 1;
72-
static const int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 2;
73-
static const int IS_USING_ARCHIVE = 1 << 3;
71+
static const int IS_DUMPING_METHOD_HANDLES = 1 << 1;
72+
static const int IS_DUMPING_STATIC_ARCHIVE = 1 << 2;
73+
static const int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 3;
74+
static const int IS_USING_ARCHIVE = 1 << 4;
75+
7476
static int get_status() NOT_CDS_RETURN_(0);
7577

7678
// Initialization and command-line checking

src/hotspot/share/cds/cds_globals.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
"\"archivedObjects\" of the specified class is stored in the " \
7272
"CDS archive heap") \
7373
\
74+
develop(ccstr, AOTInitTestClass, nullptr, \
75+
"For JVM internal testing only. The specified class is stored " \
76+
"in the initialized state in the AOT cache ") \
77+
\
7478
product(ccstr, DumpLoadedClassList, nullptr, \
7579
"Dump the names all loaded classes, that could be stored into " \
7680
"the CDS archive, in the specified file") \

src/hotspot/share/cds/heapShared.cpp

+20-7
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ static ArchivableStaticFieldInfo fmg_archive_subgraph_entry_fields[] = {
138138
KlassSubGraphInfo* HeapShared::_dump_time_special_subgraph;
139139
ArchivedKlassSubGraphInfoRecord* HeapShared::_run_time_special_subgraph;
140140
GrowableArrayCHeap<oop, mtClassShared>* HeapShared::_pending_roots = nullptr;
141-
GrowableArrayCHeap<OopHandle, mtClassShared>* HeapShared::_root_segments;
141+
GrowableArrayCHeap<OopHandle, mtClassShared>* HeapShared::_root_segments = nullptr;
142142
int HeapShared::_root_segment_max_size_elems;
143143
OopHandle HeapShared::_scratch_basic_type_mirrors[T_VOID+1];
144144
MetaspaceObjToOopHandleTable* HeapShared::_scratch_objects_table = nullptr;
@@ -225,10 +225,6 @@ int HeapShared::append_root(oop obj) {
225225
// No GC should happen since we aren't scanning _pending_roots.
226226
assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread");
227227

228-
if (_pending_roots == nullptr) {
229-
_pending_roots = new GrowableArrayCHeap<oop, mtClassShared>(500);
230-
}
231-
232228
return _pending_roots->append(obj);
233229
}
234230

@@ -336,6 +332,12 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra
336332
if (mirror_k != nullptr) {
337333
AOTArtifactFinder::add_cached_class(mirror_k);
338334
}
335+
} else if (java_lang_invoke_ResolvedMethodName::is_instance(obj)) {
336+
Method* m = java_lang_invoke_ResolvedMethodName::vmtarget(obj);
337+
if (m != nullptr) {
338+
InstanceKlass* method_holder = m->method_holder();
339+
AOTArtifactFinder::add_cached_class(method_holder);
340+
}
339341
}
340342
}
341343

@@ -402,6 +404,7 @@ objArrayOop HeapShared::scratch_resolved_references(ConstantPool* src) {
402404

403405
void HeapShared::init_dumping() {
404406
_scratch_objects_table = new (mtClass)MetaspaceObjToOopHandleTable();
407+
_pending_roots = new GrowableArrayCHeap<oop, mtClassShared>(500);
405408
}
406409

407410
void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) {
@@ -565,7 +568,7 @@ void HeapShared::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) {
565568
assert(success, "sanity");
566569
}
567570

568-
if (log_is_enabled(Info, cds, init)) {
571+
if (log_is_enabled(Debug, cds, init)) {
569572
ResourceMark rm;
570573
log_debug(cds, init)("copied %3d field(s) in aot-initialized mirror %s%s%s", nfields, ik->external_name(),
571574
ik->is_hidden() ? " (hidden)" : "",
@@ -781,8 +784,11 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) {
781784
#ifdef ASSERT
782785
InstanceKlass* ik = InstanceKlass::cast(orig_k);
783786
if (CDSConfig::is_dumping_method_handles()) {
787+
// -XX:AOTInitTestClass must be used carefully in regression tests to
788+
// include only classes that are safe to aot-initialize.
784789
assert(ik->class_loader() == nullptr ||
785-
HeapShared::is_lambda_proxy_klass(ik),
790+
HeapShared::is_lambda_proxy_klass(ik) ||
791+
AOTClassInitializer::has_test_class(),
786792
"we can archive only instances of boot classes or lambda proxy classes");
787793
} else {
788794
assert(ik->class_loader() == nullptr, "must be boot class");
@@ -827,6 +833,13 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) {
827833
}
828834

829835
void KlassSubGraphInfo::check_allowed_klass(InstanceKlass* ik) {
836+
#ifndef PRODUCT
837+
if (AOTClassInitializer::has_test_class()) {
838+
// The tests can cache arbitrary types of objects.
839+
return;
840+
}
841+
#endif
842+
830843
if (ik->module()->name() == vmSymbols::java_base()) {
831844
assert(ik->package() != nullptr, "classes in java.base cannot be in unnamed package");
832845
return;

src/hotspot/share/cds/metaspaceShared.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424

2525
#include "cds/aotArtifactFinder.hpp"
26+
#include "cds/aotClassInitializer.hpp"
2627
#include "cds/aotClassLinker.hpp"
2728
#include "cds/aotClassLocation.hpp"
2829
#include "cds/aotConstantPoolResolver.hpp"
@@ -736,6 +737,7 @@ bool MetaspaceShared::link_class_for_cds(InstanceKlass* ik, TRAPS) {
736737

737738
void MetaspaceShared::link_shared_classes(bool jcmd_request, TRAPS) {
738739
AOTClassLinker::initialize();
740+
AOTClassInitializer::init_test_class(CHECK);
739741

740742
if (!jcmd_request && !CDSConfig::is_dumping_final_static_archive()) {
741743
LambdaFormInvokers::regenerate_holder_classes(CHECK);

src/hotspot/share/include/jvm.h

+3
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ JVM_DumpClassListToFile(JNIEnv* env, jstring fileName);
218218
JNIEXPORT void JNICALL
219219
JVM_DumpDynamicArchive(JNIEnv* env, jstring archiveName);
220220

221+
JNIEXPORT jboolean JNICALL
222+
JVM_NeedsClassInitBarrierForCDS(JNIEnv* env, jclass cls);
223+
221224
/*
222225
* java.lang.Throwable
223226
*/

src/hotspot/share/prims/jvm.cpp

+24-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*
2323
*/
2424

25+
#include "cds/aotClassInitializer.hpp"
2526
#include "cds/cdsConfig.hpp"
2627
#include "cds/classListParser.hpp"
2728
#include "cds/classListWriter.hpp"
@@ -3349,7 +3350,6 @@ JVM_END
33493350

33503351
JVM_ENTRY(void, JVM_InitializeFromArchive(JNIEnv* env, jclass cls))
33513352
Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve(cls));
3352-
assert(k->is_klass(), "just checking");
33533353
HeapShared::initialize_from_archived_subgraph(THREAD, k);
33543354
JVM_END
33553355

@@ -3514,6 +3514,29 @@ JVM_ENTRY(void, JVM_DumpDynamicArchive(JNIEnv *env, jstring archiveName))
35143514
#endif // INCLUDE_CDS
35153515
JVM_END
35163516

3517+
JVM_ENTRY(jboolean, JVM_NeedsClassInitBarrierForCDS(JNIEnv* env, jclass cls))
3518+
#if INCLUDE_CDS
3519+
Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve(cls));
3520+
if (!k->is_instance_klass()) {
3521+
return false;
3522+
} else {
3523+
if (InstanceKlass::cast(k)->is_enum_subclass() ||
3524+
AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass::cast(k))) {
3525+
// This class will be cached in AOT-initialized state. No need for init barriers.
3526+
return false;
3527+
} else {
3528+
// If we cannot cache the class in AOT-initialized state, java.lang.invoke handles
3529+
// must emit barriers to ensure class initialization during production run.
3530+
ResourceMark rm(THREAD);
3531+
log_debug(cds)("NeedsClassInitBarrierForCDS: %s", k->external_name());
3532+
return true;
3533+
}
3534+
}
3535+
#else
3536+
return false;
3537+
#endif // INCLUDE_CDS
3538+
JVM_END
3539+
35173540
// Returns an array of all live Thread objects (VM internal JavaThreads,
35183541
// jvmti agent threads, and JNI attaching threads are skipped)
35193542
// See CR 6404306 regarding JNI attaching threads

src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,7 @@
2525

2626
package java.lang.invoke;
2727

28+
import jdk.internal.misc.CDS;
2829
import jdk.internal.misc.Unsafe;
2930
import jdk.internal.vm.annotation.ForceInline;
3031
import jdk.internal.vm.annotation.Stable;
@@ -367,9 +368,9 @@ static boolean shouldBeInitialized(MemberName member) {
367368
// It is a system class. It is probably in the process of
368369
// being initialized, but we will help it along just to be safe.
369370
UNSAFE.ensureClassInitialized(cls);
370-
return false;
371+
return CDS.needsClassInitBarrier(cls);
371372
}
372-
return UNSAFE.shouldBeInitialized(cls);
373+
return UNSAFE.shouldBeInitialized(cls) || CDS.needsClassInitBarrier(cls);
373374
}
374375

375376
private void ensureInitialized() {

src/java.base/share/classes/java/lang/invoke/VarHandles.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package java.lang.invoke;
2727

28+
import jdk.internal.misc.CDS;
2829
import sun.invoke.util.Wrapper;
2930

3031
import java.lang.foreign.MemoryLayout;
@@ -101,7 +102,7 @@ else if (type == double.class) {
101102
else {
102103
Class<?> decl = f.getDeclaringClass();
103104
var vh = makeStaticFieldVarHandle(decl, f, isWriteAllowedOnFinalFields);
104-
return maybeAdapt(UNSAFE.shouldBeInitialized(decl)
105+
return maybeAdapt((UNSAFE.shouldBeInitialized(decl) || CDS.needsClassInitBarrier(decl))
105106
? new LazyInitializingVarHandle(vh, decl)
106107
: vh);
107108
}

src/java.base/share/classes/jdk/internal/misc/CDS.java

+27-3
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@
4848
public class CDS {
4949
// Must be in sync with cdsConfig.hpp
5050
private static final int IS_DUMPING_ARCHIVE = 1 << 0;
51-
private static final int IS_DUMPING_STATIC_ARCHIVE = 1 << 1;
52-
private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 2;
53-
private static final int IS_USING_ARCHIVE = 1 << 3;
51+
private static final int IS_DUMPING_METHOD_HANDLES = 1 << 1;
52+
private static final int IS_DUMPING_STATIC_ARCHIVE = 1 << 2;
53+
private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 3;
54+
private static final int IS_USING_ARCHIVE = 1 << 4;
5455
private static final int configStatus = getCDSConfigStatus();
5556

5657
/**
@@ -342,6 +343,29 @@ private static String dumpSharedArchive(boolean isStatic, String fileName) throw
342343
return archiveFilePath;
343344
}
344345

346+
/**
347+
* Detects if we need to emit explicit class initialization checks in
348+
* AOT-cached MethodHandles and VarHandles before accessing static fields
349+
* and methods.
350+
* @see jdk.internal.misc.Unsafe::shouldBeInitialized
351+
*
352+
* @return false only if a call to {@code ensureClassInitialized} would have
353+
* no effect during the application's production run.
354+
*/
355+
public static boolean needsClassInitBarrier(Class<?> c) {
356+
if (c == null) {
357+
throw new NullPointerException();
358+
}
359+
360+
if ((configStatus & IS_DUMPING_METHOD_HANDLES) == 0) {
361+
return false;
362+
} else {
363+
return needsClassInitBarrier0(c);
364+
}
365+
}
366+
367+
private static native boolean needsClassInitBarrier0(Class<?> c);
368+
345369
/**
346370
* This class is used only by native JVM code at CDS dump time for loading
347371
* "unregistered classes", which are archived classes that are intended to

src/java.base/share/native/libjava/CDS.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -63,3 +63,9 @@ JNIEXPORT void JNICALL
6363
Java_jdk_internal_misc_CDS_dumpDynamicArchive(JNIEnv *env, jclass jcls, jstring archiveName) {
6464
JVM_DumpDynamicArchive(env, archiveName);
6565
}
66+
67+
JNIEXPORT jboolean JNICALL
68+
Java_jdk_internal_misc_CDS_needsClassInitBarrier0(JNIEnv *env, jclass ignore,
69+
jclass c) {
70+
return JVM_NeedsClassInitBarrierForCDS(env, c);
71+
}

0 commit comments

Comments
 (0)