diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp
index f50ae446363..da1d7af32db 100644
--- a/src/hotspot/share/cds/cdsConfig.cpp
+++ b/src/hotspot/share/cds/cdsConfig.cpp
@@ -561,7 +561,7 @@ bool CDSConfig::is_dumping_final_static_archive() {
bool CDSConfig::allow_only_single_java_thread() {
// See comments in JVM_StartThread()
- return is_dumping_static_archive();
+ return is_dumping_classic_static_archive() || is_dumping_final_static_archive();
}
bool CDSConfig::is_using_archive() {
diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp
index ee877c474b9..3658a28c102 100644
--- a/src/hotspot/share/cds/metaspaceShared.cpp
+++ b/src/hotspot/share/cds/metaspaceShared.cpp
@@ -823,8 +823,10 @@ void MetaspaceShared::preload_and_dump(TRAPS) {
if (CDSConfig::new_aot_flags_used()) {
if (CDSConfig::is_dumping_preimage_static_archive()) {
+ // We are in the JVM that runs the training run. Continue execution,
+ // so that it can finish all clean-up and return the correct exit
+ // code to the OS.
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
diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TrainingRun.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TrainingRun.java
new file mode 100644
index 00000000000..fd896fd6958
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/TrainingRun.java
@@ -0,0 +1,98 @@
+/*
+ * 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
+ * @summary -XX:AOTMode=record should not interfere with app execution: (1) thread creation; (2) exit code
+ * @bug 8351327
+ * @requires vm.cds.supports.aot.class.linking
+ * @comment work around JDK-8345635
+ * @requires !vm.jvmci.enabled
+ * @library /test/jdk/lib/testlibrary /test/lib
+ * @build TrainingRun
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar MyTestApp
+ * @run driver TrainingRun AOT
+ */
+
+import jdk.test.lib.cds.CDSAppTester;
+import jdk.test.lib.helpers.ClassFileInstaller;
+import jdk.test.lib.process.OutputAnalyzer;
+
+public class TrainingRun {
+ static final String appJar = ClassFileInstaller.getJarPath("app.jar");
+ static final String mainClass = "MyTestApp";
+
+ public static void main(String[] args) throws Exception {
+ (new Tester()).run(args);
+ }
+
+ static class Tester extends CDSAppTester {
+ public Tester() {
+ super(mainClass);
+
+ // CDSAppTester usually wants the app to return exit value 0, but this test
+ // checks whether the training run can return 2.
+ setCheckExitValue(false);
+ }
+
+ @Override
+ public String classpath(RunMode runMode) {
+ return appJar;
+ }
+
+ @Override
+ public String[] appCommandLine(RunMode runMode) {
+ return new String[] {
+ mainClass,
+ };
+ }
+
+ @Override
+ public void checkExecution(OutputAnalyzer out, RunMode runMode) {
+ if (runMode.isApplicationExecuted()) {
+ out.shouldHaveExitValue(2);
+ out.shouldContain("Hello: x is 1");
+ }
+ }
+ }
+}
+
+class MyTestApp {
+ volatile static int x = 0;
+
+ public static void main(String args[]) throws Exception {
+ Thread t = new Thread(() -> {
+ x = 1;
+ });
+ t.start();
+ t.join();
+
+ if (x != 1) {
+ throw new RuntimeException("x should be 1 but is " + x);
+ }
+ System.out.println("Hello: x is " + x);
+ System.out.println("I am calling System.exit(2)");
+ System.exit(2);
+ }
+}
diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java
index 7b4ad6eb5c4..d9f28ed2455 100644
--- a/test/lib/jdk/test/lib/cds/CDSAppTester.java
+++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java
@@ -56,7 +56,6 @@ abstract public class CDSAppTester {
throw new SkippedException("Tests based on CDSAppTester should be excluded when -Dtest.dynamic.cds.archive is specified");
}
- // Old workflow
this.name = name;
classListFile = name() + ".classlist";
classListFileLog = classListFile + ".log";
@@ -89,7 +88,7 @@ abstract public class CDSAppTester {
TRAINING, // -XX:DumpLoadedClassList OR {-XX:AOTMode=create -XX:AOTConfiguration}
DUMP_STATIC, // -Xshare:dump
DUMP_DYNAMIC, // -XX:ArchiveClassesArExit
- ASSEMBLY, // JEP 483
+ ASSEMBLY, // JEP 483 (assembly phase, app logic not executed)
PRODUCTION; // Running with the CDS archive produced from the above steps
public boolean isStaticDump() {
@@ -98,6 +97,24 @@ abstract public class CDSAppTester {
public boolean isProductionRun() {
return this == PRODUCTION;
}
+
+ // When CDSAppTester::checkExecution(out, runMode)
is called, has the application been
+ // executed? If so, out
should contain logs printed by the application's own logic.
+ public boolean isApplicationExecuted() {
+ return (this != ASSEMBLY) && (this != DUMP_STATIC);
+ }
+ }
+
+ public boolean isDumping(RunMode runMode) {
+ if (isStaticWorkflow()) {
+ return runMode == RunMode.DUMP_STATIC;
+ } else if (isDynamicWorkflow()) {
+ return runMode == RunMode.DUMP_DYNAMIC;
+ } else if (isAOTWorkflow()) {
+ return runMode == RunMode.TRAINING || runMode == RunMode.ASSEMBLY;
+ } else {
+ return false;
+ }
}
public final String name() {
@@ -184,7 +201,9 @@ abstract public class CDSAppTester {
"-XX:AOTConfiguration=" + aotConfigurationFile,
"-cp", classpath(runMode),
logToFile(aotConfigurationFileLog,
- "class+load=debug"));
+ "class+load=debug",
+ "cds=debug",
+ "cds+class=debug"));
cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode));
return executeAndCheck(cmdLine, runMode, aotConfigurationFile, aotConfigurationFileLog);
}