8351319: AOT cache support for custom class loaders broken since JDK-8348426

Reviewed-by: ccheung, matsaave, jrose
This commit is contained in:
Ioi Lam 2025-04-09 20:57:15 +00:00
parent 4954a336f8
commit e3f26b056e
13 changed files with 185 additions and 34 deletions

View File

@ -152,10 +152,11 @@ void AOTArtifactFinder::find_artifacts() {
SystemDictionaryShared::dumptime_table()->iterate_all_live_classes([&] (InstanceKlass* k, DumpTimeClassInfo& info) {
if (!info.is_excluded() && _seen_classes->get(k) == nullptr) {
info.set_excluded();
if (log_is_enabled(Info, cds)) {
info.set_has_checked_exclusion();
if (log_is_enabled(Debug, cds)) {
ResourceMark rm;
log_info(cds)("Skipping %s: %s class", k->name()->as_C_string(),
k->is_hidden() ? "Hidden" : "AOT tooling");
log_debug(cds)("Skipping %s: %s class", k->name()->as_C_string(),
k->is_hidden() ? "Unreferenced hidden" : "AOT tooling");
}
}
});
@ -211,6 +212,10 @@ void AOTArtifactFinder::add_cached_instance_class(InstanceKlass* ik) {
_seen_classes->put_if_absent(ik, &created);
if (created) {
_all_cached_classes->append(ik);
if (CDSConfig::is_dumping_final_static_archive() && ik->is_shared_unregistered_class()) {
// The following are not appliable to unregistered classes
return;
}
scan_oops_in_instance_class(ik);
if (ik->is_hidden() && CDSConfig::is_initing_classes_at_dump_time()) {
bool succeed = AOTClassLinker::try_add_candidate(ik);

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2021, 2023, 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
@ -28,6 +28,7 @@
#include "cds/dumpTimeClassInfo.hpp"
#include "cds/cdsConfig.hpp"
#include "classfile/systemDictionaryShared.hpp"
#include "classfile/classLoaderData.inline.hpp"
#include "oops/instanceKlass.hpp"
@ -44,7 +45,10 @@ void DumpTimeSharedClassTable::iterate_all_live_classes(Function function) const
auto wrapper = [&] (InstanceKlass* k, DumpTimeClassInfo& info) {
assert(SafepointSynchronize::is_at_safepoint(), "invariant");
assert_lock_strong(DumpTimeTable_lock);
if (k->is_loader_alive()) {
if (CDSConfig::is_dumping_final_static_archive() && !k->is_loaded()) {
assert(k->is_shared_unregistered_class(), "must be");
function(k, info);
} else if (k->is_loader_alive()) {
function(k, info);
assert(k->is_loader_alive(), "must not change");
} else {

View File

@ -102,7 +102,11 @@ void FinalImageRecipes::load_all_classes(TRAPS) {
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()) {
if (ik->is_shared_unregistered_class()) {
SystemDictionaryShared::init_dumptime_info(ik);
SystemDictionaryShared::add_unregistered_class(THREAD, ik);
SystemDictionaryShared::copy_unregistered_class_size_and_crc32(ik);
} else if (!ik->is_hidden()) {
Klass* actual = SystemDictionary::resolve_or_fail(ik->name(), class_loader, true, CHECK);
if (actual != ik) {
ResourceMark rm(THREAD);

View File

@ -465,6 +465,21 @@ bool SystemDictionaryShared::add_unregistered_class(Thread* current, InstanceKla
return (klass == *v);
}
void SystemDictionaryShared::copy_unregistered_class_size_and_crc32(InstanceKlass* klass) {
precond(CDSConfig::is_dumping_final_static_archive());
precond(klass->is_shared());
// A shared class must have a RunTimeClassInfo record
const RunTimeClassInfo* record = find_record(&_static_archive._unregistered_dictionary,
nullptr, klass->name());
precond(record != nullptr);
precond(record->klass() == klass);
DumpTimeClassInfo* info = get_info(klass);
info->_clsfile_size = record->crc()->_clsfile_size;
info->_clsfile_crc32 = record->crc()->_clsfile_crc32;
}
void SystemDictionaryShared::set_shared_class_misc_info(InstanceKlass* k, ClassFileStream* cfs) {
assert(CDSConfig::is_dumping_archive(), "sanity");
assert(!is_builtin(k), "must be unregistered class");
@ -667,7 +682,7 @@ bool SystemDictionaryShared::should_be_excluded(Klass* k) {
}
void SystemDictionaryShared::finish_exclusion_checks() {
if (CDSConfig::is_dumping_dynamic_archive()) {
if (CDSConfig::is_dumping_dynamic_archive() || CDSConfig::is_dumping_preimage_static_archive()) {
// Do this first -- if a base class is excluded due to duplication,
// all of its subclasses will also be excluded.
ResourceMark rm;

View File

@ -248,6 +248,7 @@ public:
return (k->shared_classpath_index() != UNREGISTERED_INDEX);
}
static bool add_unregistered_class(Thread* current, InstanceKlass* k);
static void copy_unregistered_class_size_and_crc32(InstanceKlass* klass);
static void finish_exclusion_checks();
static DumpTimeSharedClassTable* dumptime_table() { return _dumptime_table; }

View File

@ -476,9 +476,23 @@ void ConstantPool::remove_unshareable_info() {
return;
}
bool update_resolved_reference = true;
if (CDSConfig::is_dumping_final_static_archive()) {
ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this);
InstanceKlass* src_holder = src_cp->pool_holder();
if (src_holder->is_shared_unregistered_class()) {
// Unregistered classes are not loaded in the AOT assembly phase. The resolved reference length
// is already saved during the training run.
precond(!src_holder->is_loaded());
precond(resolved_reference_length() >= 0);
precond(resolved_references() == nullptr);
update_resolved_reference = false;
}
}
// resolved_references(): remember its length. If it cannot be restored
// from the archived heap objects at run time, we need to dynamically allocate it.
if (cache() != nullptr) {
if (update_resolved_reference && cache() != nullptr) {
set_resolved_reference_length(
resolved_references() != nullptr ? resolved_references()->length() : 0);
set_resolved_references(OopHandle());

View File

@ -828,9 +828,15 @@ void Klass::remove_java_mirror() {
if (CDSConfig::is_dumping_heap()) {
Klass* src_k = ArchiveBuilder::current()->get_source_addr(this);
oop orig_mirror = src_k->java_mirror();
oop scratch_mirror = HeapShared::scratch_java_mirror(orig_mirror);
if (scratch_mirror != nullptr) {
_archived_mirror_index = HeapShared::append_root(scratch_mirror);
if (orig_mirror == nullptr) {
assert(CDSConfig::is_dumping_final_static_archive(), "sanity");
assert(is_instance_klass(), "sanity");
assert(InstanceKlass::cast(this)->is_shared_unregistered_class(), "sanity");
} else {
oop scratch_mirror = HeapShared::scratch_java_mirror(orig_mirror);
if (scratch_mirror != nullptr) {
_archived_mirror_index = HeapShared::append_root(scratch_mirror);
}
}
}
#endif

View File

@ -31,11 +31,14 @@
* @requires vm.cds.supports.aot.class.linking
* @comment work around JDK-8345635
* @requires !vm.jvmci.enabled
* @library /test/jdk/lib/testlibrary /test/lib
* @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes
* @build InitiatingLoaderTester BadOldClassA BadOldClassB
* @build BulkLoaderTest
* @build jdk.test.whitebox.WhiteBox BulkLoaderTest SimpleCusty
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester
* BadOldClassA BadOldClassB
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar
* SimpleCusty
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox
* @run driver BulkLoaderTest STATIC
*/
@ -44,13 +47,15 @@
* @requires vm.cds.supports.aot.class.linking
* @comment work around JDK-8345635
* @requires !vm.jvmci.enabled
* @library /test/jdk/lib/testlibrary /test/lib
* @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes
* @build InitiatingLoaderTester BadOldClassA BadOldClassB
* @build jdk.test.whitebox.WhiteBox BulkLoaderTest
* @build jdk.test.whitebox.WhiteBox BulkLoaderTest SimpleCusty
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester
* BadOldClassA BadOldClassB
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. BulkLoaderTest DYNAMIC
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar
* SimpleCusty
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:WhiteBox.jar BulkLoaderTest DYNAMIC
*/
/*
@ -58,16 +63,21 @@
* @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
* @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes
* @build jdk.test.whitebox.WhiteBox InitiatingLoaderTester BadOldClassA BadOldClassB
* @build BulkLoaderTest SimpleCusty
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester
* BadOldClassA BadOldClassB
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar
* SimpleCusty
* @run driver BulkLoaderTest AOT
*/
import java.io.File;
import java.lang.StackWalker.StackFrame;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -76,6 +86,7 @@ import java.util.Set;
import jdk.test.lib.cds.CDSAppTester;
import jdk.test.lib.helpers.ClassFileInstaller;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.whitebox.WhiteBox;
public class BulkLoaderTest {
static final String appJar = ClassFileInstaller.getJarPath("BulkLoaderTestApp.jar");
@ -114,6 +125,7 @@ public class BulkLoaderTest {
static class Tester extends CDSAppTester {
public Tester() {
super(mainClass);
useWhiteBox(ClassFileInstaller.getJarPath("WhiteBox.jar"));
}
@Override
@ -124,7 +136,7 @@ public class BulkLoaderTest {
@Override
public String[] vmArgs(RunMode runMode) {
return new String[] {
"-Xlog:cds,cds+aot+load",
"-Xlog:cds,cds+aot+load,cds+class=debug",
"-XX:+AOTClassLinking",
};
}
@ -140,6 +152,12 @@ public class BulkLoaderTest {
public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
if (isAOTWorkflow() && runMode == RunMode.TRAINING) {
out.shouldContain("Skipping BadOldClassA: Unlinked class not supported by AOTConfiguration");
out.shouldContain("Skipping SimpleCusty: Duplicated unregistered class");
}
if (isDumping(runMode)) {
// Check that we are archiving classes for custom class loaders.
out.shouldMatch("cds,class.* SimpleCusty");
}
}
}
@ -152,6 +170,7 @@ class BulkLoaderTestApp {
checkClasses();
checkInitiatingLoader();
checkOldClasses();
checkCustomLoader();
}
// Check the ClassLoader/Module/Package/ProtectionDomain/CodeSource of classes that are aot-linked
@ -275,6 +294,38 @@ class BulkLoaderTestApp {
System.out.println("Caught VerifyError for BadOldClassB: " + e);
}
}
static void checkCustomLoader() throws Exception {
WhiteBox wb = WhiteBox.getWhiteBox();
for (int i = 0; i < 2; i++) {
Object o = initFromCustomLoader();
System.out.println(o);
Class c = o.getClass();
if (wb.isSharedClass(BulkLoaderTestApp.class)) {
// We are running with BulkLoaderTestApp from the AOT cache (or CDS achive)
if (i == 0) {
if (!wb.isSharedClass(c)) {
throw new RuntimeException("The first loader should load SimpleCusty from AOT cache (or CDS achive)");
}
} else {
if (wb.isSharedClass(c)) {
throw new RuntimeException("The second loader should not load SimpleCusty from AOT cache (or CDS achive)");
}
}
}
}
}
static Object initFromCustomLoader() throws Exception {
String path = "cust.jar";
URL url = new File(path).toURI().toURL();
URL[] urls = new URL[] {url};
URLClassLoader urlClassLoader =
new URLClassLoader("MyLoader", urls, null);
Class c = Class.forName("SimpleCusty", true, urlClassLoader);
return c.newInstance();
}
}
class MyUtil {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, 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
@ -51,7 +51,7 @@ public class LambdaCustomLoader extends DynamicArchiveTestBase {
"-Xlog:class+load,cds=debug,cds+dynamic",
"-cp", appJar, mainClass, appJar, "init", "keep-alive")
.assertNormalExit(output -> {
output.shouldMatch("Skipping.LambHello[$][$]Lambda.*0x.*:.Hidden.class")
output.shouldMatch("Skipping.LambHello[$][$]Lambda.*0x.*:.Unreferenced.hidden.class")
.shouldHaveExitValue(0);
});

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2024, 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
@ -59,8 +59,8 @@ public class LambdaProxyCallerIsHidden extends DynamicArchiveTestBase {
"-Xlog:class+load,cds+dynamic,cds=debug",
"-cp", appJar, mainClass)
.assertNormalExit(output -> {
output.shouldMatch("Skipping.LambdaHello_0x.*[$][$]Lambda.*:.Hidden.class")
.shouldMatch("Skipping.LambdaHello.0x.*:.Hidden.class")
output.shouldMatch("Skipping.LambdaHello_0x.*[$][$]Lambda.*:.Unreferenced.hidden.class")
.shouldMatch("Skipping.LambdaHello.0x.*:.Unreferenced.hidden.class")
.shouldHaveExitValue(0);
});

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2024, 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
@ -57,7 +57,7 @@ public class RegularHiddenClass extends DynamicArchiveTestBase {
"-Xlog:class+load=debug,cds+dynamic,cds=debug",
"-cp", appJar, mainClass, "keep-alive")
.assertNormalExit(output -> {
output.shouldMatch("cds.*Skipping.TestClass.0x.*Hidden.class")
output.shouldMatch("cds.*Skipping.TestClass.0x.*Unreferenced.hidden.class")
.shouldNotMatch("cds.dynamic.*Archiving.hidden.TestClass.*")
.shouldHaveExitValue(0);
});

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
// This class is to be loaded by a custom class loader.
public class SimpleCusty {
public String toString() {
return "Instance of SimpleCusty";
}
}

View File

@ -50,6 +50,7 @@ abstract public class CDSAppTester {
private final String dynamicArchiveFileLog;
private final String tempBaseArchiveFile;
private int numProductionRuns = 0;
private String whiteBoxJar = null;
public CDSAppTester(String name) {
if (CDSTestUtils.DYNAMIC_DUMP) {
@ -150,6 +151,10 @@ abstract public class CDSAppTester {
checkExitValue = b;
}
public final void useWhiteBox(String whiteBoxJar) {
this.whiteBoxJar = whiteBoxJar;
}
public final boolean isStaticWorkflow() {
return workflow == Workflow.STATIC;
}
@ -199,6 +204,12 @@ abstract public class CDSAppTester {
return output;
}
private String[] addCommonVMArgs(RunMode runMode, String[] cmdLine) {
cmdLine = addClassOrModulePath(runMode, cmdLine);
cmdLine = addWhiteBox(cmdLine);
return cmdLine;
}
private String[] addClassOrModulePath(RunMode runMode, String[] cmdLine) {
String cp = classpath(runMode);
if (cp == null) {
@ -214,6 +225,16 @@ abstract public class CDSAppTester {
return cmdLine;
}
private String[] addWhiteBox(String[] cmdLine) {
if (whiteBoxJar != null) {
cmdLine = StringArrayUtils.concat(cmdLine,
"-XX:+UnlockDiagnosticVMOptions",
"-XX:+WhiteBoxAPI",
"-Xbootclasspath/a:" + whiteBoxJar);
}
return cmdLine;
}
private OutputAnalyzer recordAOTConfiguration() throws Exception {
RunMode runMode = RunMode.TRAINING;
String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode),
@ -223,7 +244,7 @@ abstract public class CDSAppTester {
"class+load=debug",
"cds=debug",
"cds+class=debug"));
cmdLine = addClassOrModulePath(runMode, cmdLine);
cmdLine = addCommonVMArgs(runMode, cmdLine);
cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
return executeAndCheck(cmdLine, runMode, aotConfigurationFile, aotConfigurationFileLog);
}
@ -235,7 +256,7 @@ abstract public class CDSAppTester {
"-XX:DumpLoadedClassList=" + classListFile,
logToFile(classListFileLog,
"class+load=debug"));
cmdLine = addClassOrModulePath(runMode, cmdLine);
cmdLine = addCommonVMArgs(runMode, cmdLine);
cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
return executeAndCheck(cmdLine, runMode, classListFile, classListFileLog);
}
@ -253,7 +274,7 @@ abstract public class CDSAppTester {
"cds+class=debug",
"cds+heap=warning",
"cds+resolve=debug"));
cmdLine = addClassOrModulePath(runMode, cmdLine);
cmdLine = addCommonVMArgs(runMode, cmdLine);
cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
return executeAndCheck(cmdLine, runMode, staticArchiveFile, staticArchiveFileLog);
}
@ -271,7 +292,7 @@ abstract public class CDSAppTester {
"cds+class=debug",
"cds+heap=warning",
"cds+resolve=debug"));
cmdLine = addClassOrModulePath(runMode, cmdLine);
cmdLine = addCommonVMArgs(runMode, cmdLine);
cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
return executeAndCheck(cmdLine, runMode, aotCacheFile, aotCacheFileLog);
}
@ -317,7 +338,7 @@ abstract public class CDSAppTester {
"cds+class=debug",
"cds+resolve=debug",
"class+load=debug"));
cmdLine = addClassOrModulePath(runMode, cmdLine);
cmdLine = addCommonVMArgs(runMode, cmdLine);
}
if (baseArchive != null) {
cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + baseArchive);
@ -342,7 +363,7 @@ abstract public class CDSAppTester {
"-XX:+UnlockDiagnosticVMOptions",
"-XX:VerifyArchivedFields=2", // make sure archived heap objects are good.
logToFile(productionRunLog(), "cds"));
cmdLine = addClassOrModulePath(runMode, cmdLine);
cmdLine = addCommonVMArgs(runMode, cmdLine);
if (isStaticWorkflow()) {
cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + staticArchiveFile);