diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index d6ac0877eeb..7750081ec8a 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -787,10 +787,34 @@ InstanceKlass* SystemDictionaryShared::get_shared_lambda_proxy_class(InstanceKla MutexLocker ml(CDSLambda_lock, Mutex::_no_safepoint_check_flag); LambdaProxyClassKey key(caller_ik, invoked_name, invoked_type, method_type, member_method, instantiated_method_type); + + // Try to retrieve the lambda proxy class from static archive. const RunTimeLambdaProxyClassInfo* info = _static_archive.lookup_lambda_proxy_class(&key); - if (info == nullptr) { - info = _dynamic_archive.lookup_lambda_proxy_class(&key); + InstanceKlass* proxy_klass = retrieve_lambda_proxy_class(info); + if (proxy_klass == nullptr) { + if (info != nullptr && log_is_enabled(Debug, cds)) { + ResourceMark rm; + log_debug(cds)("Used all static archived lambda proxy classes for: %s %s%s", + caller_ik->external_name(), invoked_name->as_C_string(), invoked_type->as_C_string()); + } + } else { + return proxy_klass; } + + // Retrieving from static archive is unsuccessful, try dynamic archive. + info = _dynamic_archive.lookup_lambda_proxy_class(&key); + proxy_klass = retrieve_lambda_proxy_class(info); + if (proxy_klass == nullptr) { + if (info != nullptr && log_is_enabled(Debug, cds)) { + ResourceMark rm; + log_debug(cds)("Used all dynamic archived lambda proxy classes for: %s %s%s", + caller_ik->external_name(), invoked_name->as_C_string(), invoked_type->as_C_string()); + } + } + return proxy_klass; +} + +InstanceKlass* SystemDictionaryShared::retrieve_lambda_proxy_class(const RunTimeLambdaProxyClassInfo* info) { InstanceKlass* proxy_klass = nullptr; if (info != nullptr) { InstanceKlass* curr_klass = info->proxy_klass_head(); @@ -810,12 +834,6 @@ InstanceKlass* SystemDictionaryShared::get_shared_lambda_proxy_class(InstanceKla ResourceMark rm; log_debug(cds)("Loaded lambda proxy: %s ", proxy_klass->external_name()); } - } else { - if (log_is_enabled(Debug, cds)) { - ResourceMark rm; - log_debug(cds)("Used all archived lambda proxy classes for: %s %s%s", - caller_ik->external_name(), invoked_name->as_C_string(), invoked_type->as_C_string()); - } } } return proxy_klass; diff --git a/src/hotspot/share/classfile/systemDictionaryShared.hpp b/src/hotspot/share/classfile/systemDictionaryShared.hpp index da167a19c6e..c0da32d4e5a 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp @@ -199,6 +199,7 @@ private: static bool check_for_exclusion_impl(InstanceKlass* k); static void remove_dumptime_info(InstanceKlass* k) NOT_CDS_RETURN; static bool has_been_redefined(InstanceKlass* k); + static InstanceKlass* retrieve_lambda_proxy_class(const RunTimeLambdaProxyClassInfo* info) NOT_CDS_RETURN_(nullptr); DEBUG_ONLY(static bool _class_loading_may_happen;) diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index f651d41d0b5..593c66fab70 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -255,6 +255,20 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; private Class spinInnerClass() throws LambdaConversionException { // CDS does not handle disableEagerInitialization or useImplMethodHandle if (!disableEagerInitialization && !useImplMethodHandle) { + if (CDS.isSharingEnabled()) { + // load from CDS archive if present + Class innerClass = LambdaProxyClassArchive.find(targetClass, + interfaceMethodName, + factoryType, + interfaceMethodType, + implementation, + dynamicMethodType, + isSerializable, + altInterfaces, + altMethods); + if (innerClass != null) return innerClass; + } + // include lambda proxy class in CDS archive at dump time if (CDS.isDumpingArchive()) { Class innerClass = generateInnerClass(); @@ -271,17 +285,6 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; return innerClass; } - // load from CDS archive if present - Class innerClass = LambdaProxyClassArchive.find(targetClass, - interfaceMethodName, - factoryType, - interfaceMethodType, - implementation, - dynamicMethodType, - isSerializable, - altInterfaces, - altMethods); - if (innerClass != null) return innerClass; } return generateInnerClass(); } diff --git a/src/java.base/share/classes/java/lang/invoke/LambdaProxyClassArchive.java b/src/java.base/share/classes/java/lang/invoke/LambdaProxyClassArchive.java index 6f9f92262cc..ef79f4d473c 100644 --- a/src/java.base/share/classes/java/lang/invoke/LambdaProxyClassArchive.java +++ b/src/java.base/share/classes/java/lang/invoke/LambdaProxyClassArchive.java @@ -101,9 +101,6 @@ final class LambdaProxyClassArchive { boolean isSerializable, Class[] altInterfaces, MethodType[] altMethods) { - if (CDS.isDumpingArchive()) - throw new IllegalStateException("cannot load class from CDS archive at dump time"); - if (!loadedByBuiltinLoader(caller) || !CDS.isSharingEnabled() || isSerializable || altInterfaces.length > 0 || altMethods.length > 0) return null; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/LambdasInTwoArchives.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/LambdasInTwoArchives.java new file mode 100644 index 00000000000..1b77c53b25e --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/LambdasInTwoArchives.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023, 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 + * @bug 8307468 + * @summary Test archiving of lambda proxy classes with the same LambdaProxyClassKey + * (see cds/lambdaProxyClassDictionary.hpp). If some lambda proxy classes + * are already in the static archive, during dynamic dump with the static archive, + * the ones in the static archive should not be generated and archived + * in the dynamic archive. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds + * /test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/test-classes + * @build LambdasWithSameKey + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar lambdas_same_key.jar LambdasWithSameKey + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. LambdasInTwoArchives + */ + +import java.io.File; +import java.util.List; +import java.util.regex.Pattern; + +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class LambdasInTwoArchives extends DynamicArchiveTestBase { + static final String lambdaPattern = + ".*cds.class.*klasses.*LambdasWithSameKey[$][$]Lambda.*/0x.*hidden"; + static final String loadFromStatic = + ".*class.load.*LambdasWithSameKey[$][$]Lambda/0x.*source:.*shared.*objects.*file"; + static final String loadFromTop = loadFromStatic + ".*(top).*"; + static final String usedAllStatic = + "Used all static archived lambda proxy classes for: LambdasWithSameKey"; + + public static void main(String[] args) throws Exception { + runTest(LambdasInTwoArchives::test); + } + + static void checkLambdas(OutputAnalyzer output, String matchPattern, int numLambdas) throws Exception { + List lines = output.asLines(); + Pattern pattern = Pattern.compile(matchPattern); + int count = 0; + for (int i = 0; i < lines.size(); i++) { + if (pattern.matcher(lines.get(i)).matches()) { + count++; + } + } + if (count != numLambdas) { + throw new RuntimeException("Expecting " + numLambdas + " lambda proxy classes, but got " + count); + } + } + + static void test() throws Exception { + String classListFileName = "lambda-classes.list"; + File fileList = new File(classListFileName); + if (fileList.exists()) { + fileList.delete(); + } + String appJar = ClassFileInstaller.getJarPath("lambdas_same_key.jar"); + String mainClass = "LambdasWithSameKey"; + // Generate a class list for static dump. + // Note that the class list contains one less lambda proxy class comparing + // with running the LambdasWithSameKey app with the "run" argument. + String[] launchArgs = { + "-Xshare:off", + "-XX:DumpLoadedClassList=" + classListFileName, + "-Xlog:cds", + "-Xlog:cds+lambda", + "-cp", appJar, mainClass}; + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(launchArgs); + OutputAnalyzer oa = TestCommon.executeAndLog(pb, "lambda-classes"); + oa.shouldHaveExitValue(0); + + String logOptions = "-Xlog:cds=debug,class+load,cds+class=debug"; + String baseArchiveName = CDSTestUtils.getOutputFileName("lambda-base.jsa"); + // Static dump based on the class list. + dumpBaseArchive(baseArchiveName, + "-XX:SharedClassListFile=" + classListFileName, + logOptions, + "-cp", appJar, mainClass) + // Expects 2 lambda proxy classes with LambdasWithSameKey as the + // caller class in the static dump log. + .assertNormalExit(output -> checkLambdas(output, lambdaPattern, 2)); + + String topArchiveName = getNewArchiveName("lambda-classes-top"); + + // Dynamic dump with the static archive. + dump2(baseArchiveName, topArchiveName, + logOptions, + "-cp", appJar, mainClass, "run") + // Expects only 1 lambda proxy class with LambdasWithSameKey as the + // caller class in the dynamic dump log. + .assertNormalExit(output -> checkLambdas(output, lambdaPattern, 1)) + .assertNormalExit(output -> { + output.shouldContain(usedAllStatic); + }); + + // Run with both static and dynamic archives. + run2(baseArchiveName, topArchiveName, + logOptions, + "-cp", appJar, mainClass, "run") + // Two lambda proxy classes should be loaded from the static archive. + .assertNormalExit(output -> checkLambdas(output, loadFromStatic, 2)) + .assertNormalExit(output -> { output.shouldContain(usedAllStatic); }) + // One lambda proxy class should be loaded from the dynamic archive. + .assertNormalExit(output -> checkLambdas(output, loadFromTop, 1)); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/UsedAllArchivedLambdas.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/UsedAllArchivedLambdas.java index 4836bf5b51c..46efaf4893d 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/UsedAllArchivedLambdas.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/UsedAllArchivedLambdas.java @@ -66,7 +66,7 @@ public class UsedAllArchivedLambdas extends DynamicArchiveTestBase { "-Xlog:cds=debug", "-cp", appJar, mainClass, "run") .assertNormalExit(output -> { - output.shouldContain("Used all archived lambda proxy classes for: UsedAllArchivedLambdasApp run()Ljava/lang/Runnable;") + output.shouldContain("Used all dynamic archived lambda proxy classes for: UsedAllArchivedLambdasApp run()Ljava/lang/Runnable;") .shouldHaveExitValue(0); }); } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/test-classes/LambdasWithSameKey.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/test-classes/LambdasWithSameKey.java new file mode 100644 index 00000000000..d303ccb1bec --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/test-classes/LambdasWithSameKey.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023, 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 for generating lambda proxy classes with the same invoke dynamic + * info such as: caller class, invoked name, invoked type, method type, etc. + * + */ + +public class LambdasWithSameKey { + public static void main(String args[]) { + boolean isRun = (args.length == 1 && args[0].equals("run")) ? true : false; + {Runnable run1 = LambdasWithSameKey::myrun; run1.run();} + {Runnable run1 = LambdasWithSameKey::myrun; run1.run();} + if (isRun) { + {Runnable run1 = LambdasWithSameKey::myrun; run1.run();} + } + } + + static void myrun() { + System.out.println("myrun"); + } +}