From 41a2d49f0a1ed298b8ab023ce634335464454fe7 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Fri, 15 Nov 2024 22:28:54 +0000 Subject: [PATCH] 8331497: Implement JEP 483: Ahead-of-Time Class Loading & Linking Reviewed-by: jrose, kvn, heidinga, asmehra, vlivanov --- make/RunTests.gmk | 72 +- src/hotspot/share/cds/aotClassInitializer.cpp | 359 ++++++++ src/hotspot/share/cds/aotClassInitializer.hpp | 46 + src/hotspot/share/cds/aotClassLinker.cpp | 319 +++++++ src/hotspot/share/cds/aotClassLinker.hpp | 129 +++ .../share/cds/aotConstantPoolResolver.cpp | 573 ++++++++++++ ...linker.hpp => aotConstantPoolResolver.hpp} | 40 +- .../share/cds/aotLinkedClassBulkLoader.cpp | 397 ++++++++ .../share/cds/aotLinkedClassBulkLoader.hpp | 69 ++ src/hotspot/share/cds/aotLinkedClassTable.cpp | 40 + src/hotspot/share/cds/aotLinkedClassTable.hpp | 77 ++ src/hotspot/share/cds/archiveBuilder.cpp | 174 +++- src/hotspot/share/cds/archiveBuilder.hpp | 7 +- src/hotspot/share/cds/archiveHeapLoader.cpp | 4 - src/hotspot/share/cds/archiveHeapWriter.cpp | 18 +- src/hotspot/share/cds/archiveUtils.cpp | 9 + src/hotspot/share/cds/archiveUtils.hpp | 5 + src/hotspot/share/cds/archiveUtils.inline.hpp | 20 +- src/hotspot/share/cds/cdsConfig.cpp | 160 +++- src/hotspot/share/cds/cdsConfig.hpp | 43 +- src/hotspot/share/cds/cdsEnumKlass.cpp | 2 +- src/hotspot/share/cds/cdsHeapVerifier.cpp | 117 ++- src/hotspot/share/cds/cds_globals.hpp | 24 + src/hotspot/share/cds/classListParser.cpp | 34 +- src/hotspot/share/cds/classListWriter.cpp | 30 +- src/hotspot/share/cds/classPrelinker.cpp | 345 ------- src/hotspot/share/cds/dumpAllocStats.cpp | 7 + src/hotspot/share/cds/dumpAllocStats.hpp | 12 + src/hotspot/share/cds/dumpTimeClassInfo.hpp | 12 +- src/hotspot/share/cds/dynamicArchive.cpp | 21 +- src/hotspot/share/cds/filemap.cpp | 78 +- src/hotspot/share/cds/filemap.hpp | 6 +- src/hotspot/share/cds/heapShared.cpp | 856 ++++++++++++++++-- src/hotspot/share/cds/heapShared.hpp | 51 +- src/hotspot/share/cds/lambdaFormInvokers.cpp | 17 +- src/hotspot/share/cds/metaspaceShared.cpp | 107 ++- src/hotspot/share/cds/metaspaceShared.hpp | 6 + src/hotspot/share/cds/runTimeClassInfo.cpp | 2 +- src/hotspot/share/cds/runTimeClassInfo.hpp | 6 +- src/hotspot/share/classfile/classLoader.cpp | 43 +- src/hotspot/share/classfile/classLoader.hpp | 2 + src/hotspot/share/classfile/javaClasses.cpp | 33 +- src/hotspot/share/classfile/javaClasses.hpp | 2 + .../share/classfile/systemDictionary.cpp | 90 +- .../share/classfile/systemDictionary.hpp | 10 + .../classfile/systemDictionaryShared.cpp | 182 +++- .../classfile/systemDictionaryShared.hpp | 8 +- src/hotspot/share/classfile/vmClassMacros.hpp | 3 +- src/hotspot/share/classfile/vmClasses.cpp | 4 + src/hotspot/share/classfile/vmSymbols.hpp | 5 + .../share/interpreter/interpreterRuntime.cpp | 17 + .../share/interpreter/interpreterRuntime.hpp | 7 +- src/hotspot/share/logging/logTag.hpp | 1 + src/hotspot/share/memory/iterator.inline.hpp | 9 +- src/hotspot/share/oops/constantPool.cpp | 104 ++- src/hotspot/share/oops/constantPool.hpp | 10 +- src/hotspot/share/oops/cpCache.cpp | 67 +- src/hotspot/share/oops/cpCache.hpp | 3 +- src/hotspot/share/oops/instanceKlass.cpp | 146 ++- src/hotspot/share/oops/instanceKlass.hpp | 8 + src/hotspot/share/oops/klass.hpp | 24 +- src/hotspot/share/oops/method.cpp | 11 + src/hotspot/share/oops/method.hpp | 1 + src/hotspot/share/prims/jvm.cpp | 2 +- src/hotspot/share/prims/jvmtiExport.cpp | 10 + src/hotspot/share/prims/jvmtiExport.hpp | 1 + src/hotspot/share/runtime/arguments.cpp | 4 + .../share/runtime/flags/jvmFlagAccess.cpp | 13 +- .../flags/jvmFlagConstraintsRuntime.cpp | 17 +- .../flags/jvmFlagConstraintsRuntime.hpp | 3 +- src/hotspot/share/runtime/threads.cpp | 23 + .../share/classes/java/lang/Class.java | 14 + .../java/lang/invoke/LambdaFormEditor.java | 21 +- .../java/lang/invoke/MethodHandleNatives.java | 23 +- .../classes/java/lang/invoke/MethodType.java | 42 + .../java/lang/invoke/MethodTypeForm.java | 63 +- .../java/lang/invoke/StringConcatFactory.java | 2 + .../util/concurrent/ConcurrentHashMap.java | 12 +- .../classes/jdk/internal/misc/Unsafe.java | 5 + test/hotspot/jtreg/ProblemList-AotJdk.txt | 18 + test/hotspot/jtreg/TEST.ROOT | 1 + test/hotspot/jtreg/TEST.groups | 63 ++ .../jtreg/runtime/cds/SharedBaseAddress.java | 21 +- .../jtreg/runtime/cds/appcds/AOTFlags.java | 203 +++++ .../runtime/cds/appcds/ClassPathAttr.java | 17 +- .../cds/appcds/LambdaContainsOldInf.java | 8 +- .../cds/appcds/LambdaWithOldClass.java | 8 +- .../appcds/LambdaWithUseImplMethodHandle.java | 10 +- .../cds/appcds/TestParallelGCWithCDS.java | 1 + .../AOTClassLinkingVMOptions.java | 183 ++++ .../aotClassLinking/BulkLoaderTest.java | 241 +++++ .../InitiatingLoaderTester.jasm | 56 ++ .../cacheObject/ArchiveHeapTestClass.java | 202 ++++- .../customLoader/CustomClassListDump.java | 4 +- .../appcds/jvmti/ClassFileLoadHookTest.java | 20 +- .../OldClassAndRedefineClass.java | 4 +- .../resolvedConstants/AOTLinkedLambdas.java | 388 ++++++++ .../AOTLinkedVarHandles.java | 128 +++ .../resolvedConstants/ResolvedConstants.java | 211 ++++- .../StringConcatTestOld.jasm | 78 ++ .../cds/appcds/test-classes/OldConsumer.jasm | 53 ++ test/jdk/ProblemList-AotJdk.txt | 11 + .../internal/misc/CDS/ArchivedEnumTest.java | 5 +- test/jtreg-ext/requires/VMProps.java | 9 + test/lib/jdk/test/lib/cds/CDSAppTester.java | 53 +- test/lib/jdk/test/lib/cds/CDSTestUtils.java | 26 +- 106 files changed, 6601 insertions(+), 760 deletions(-) create mode 100644 src/hotspot/share/cds/aotClassInitializer.cpp create mode 100644 src/hotspot/share/cds/aotClassInitializer.hpp create mode 100644 src/hotspot/share/cds/aotClassLinker.cpp create mode 100644 src/hotspot/share/cds/aotClassLinker.hpp create mode 100644 src/hotspot/share/cds/aotConstantPoolResolver.cpp rename src/hotspot/share/cds/{classPrelinker.hpp => aotConstantPoolResolver.hpp} (73%) create mode 100644 src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp create mode 100644 src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp create mode 100644 src/hotspot/share/cds/aotLinkedClassTable.cpp create mode 100644 src/hotspot/share/cds/aotLinkedClassTable.hpp delete mode 100644 src/hotspot/share/cds/classPrelinker.cpp create mode 100644 test/hotspot/jtreg/ProblemList-AotJdk.txt create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVMOptions.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/InitiatingLoaderTester.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/StringConcatTestOld.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/test-classes/OldConsumer.jasm create mode 100644 test/jdk/ProblemList-AotJdk.txt diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 747d8f84dbc..636d1ed18b2 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -45,7 +45,7 @@ ifneq ($(TEST_VM_OPTS), ) endif $(eval $(call ParseKeywordVariable, TEST_OPTS, \ - SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR JCOV JCOV_DIFF_CHANGESET, \ + SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR JCOV JCOV_DIFF_CHANGESET AOT_JDK, \ STRING_KEYWORDS := VM_OPTIONS JAVA_OPTIONS, \ )) @@ -202,11 +202,12 @@ $(eval $(call SetTestOpt,JOBS,JTREG)) $(eval $(call SetTestOpt,TIMEOUT_FACTOR,JTREG)) $(eval $(call SetTestOpt,FAILURE_HANDLER_TIMEOUT,JTREG)) $(eval $(call SetTestOpt,REPORT,JTREG)) +$(eval $(call SetTestOpt,AOT_JDK,JTREG)) $(eval $(call ParseKeywordVariable, JTREG, \ SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR FAILURE_HANDLER_TIMEOUT \ TEST_MODE ASSERT VERBOSE RETAIN TEST_THREAD_FACTORY MAX_MEM RUN_PROBLEM_LISTS \ - RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ + RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT AOT_JDK $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \ EXTRA_PROBLEM_LISTS LAUNCHER_OPTIONS \ $(CUSTOM_JTREG_STRING_KEYWORDS), \ @@ -702,6 +703,58 @@ define SetJtregValue endif endef + +# Parameter 1 is the name of the rule. +# +# Remaining parameters are named arguments. +# VM_OPTIONS List of JVM arguments to use when creating AOT cache +# +# After calling this, the following variables are defined +# $1_AOT_TARGETS List of all targets that the test rule will need to depend on +# $1_AOT_JDK_CACHE The AOT cache file to be used to run the test with +# +SetupAot = $(NamedParamsMacroTemplate) +define SetupAotBody + $1_AOT_JDK_CONF := $$($1_TEST_SUPPORT_DIR)/aot/jdk.aotconf + $1_AOT_JDK_CACHE := $$($1_TEST_SUPPORT_DIR)/aot/jdk.aotcache + + $1_JAVA_TOOL_OPTS := $$(addprefix -J, $$($1_VM_OPTIONS)) + + $$($1_AOT_JDK_CACHE): $$(JDK_IMAGE_DIR)/release + $$(call MakeDir, $$($1_TEST_SUPPORT_DIR)/aot) + + $(foreach jtool, javac javap jlink jar, \ + $(info AOT: Create cache configuration for $(jtool)) \ + $$(call ExecuteWithLog, $$($1_TEST_SUPPORT_DIR)/aot.$(jtool), ( \ + $$(FIXPATH) $(JDK_UNDER_TEST)/bin/$(jtool) $$($1_JAVA_TOOL_OPTS) \ + -J-XX:AOTMode=record -J-XX:AOTConfiguration=$$($1_AOT_JDK_CONF).$(jtool) --help \ + )) + ) + + $$(info AOT: Copy $(JDK_UNDER_TEST)/lib/classlist to $$($1_AOT_JDK_CONF).jdk ) + $$(call ExecuteWithLog, $$($1_TEST_SUPPORT_DIR)/aot, ( \ + $$(FIXPATH) $(CP) $(JDK_UNDER_TEST)/lib/classlist $$($1_AOT_JDK_CONF).jdk \ + )) + + $$(FIXPATH) $$(CAT) $$($1_AOT_JDK_CONF).* > $$($1_AOT_JDK_CONF).temp + $$(FIXPATH) $$(CAT) $$($1_AOT_JDK_CONF).temp | $(GREP) -v '#' | $(GREP) -v '@' | $(SORT) | \ + $(SED) -e 's/id:.*//g' | uniq \ + > $$($1_AOT_JDK_CONF) + $$(FIXPATH) $$(CAT) $$($1_AOT_JDK_CONF).temp | $(GREP) '@cp' | $(SORT) \ + >> $$($1_AOT_JDK_CONF) + + $$(info AOT: Generate AOT cache $$($1_AOT_JDK_CACHE) with flags: $$($1_VM_OPTIONS)) + $$(call ExecuteWithLog, $$($1_TEST_SUPPORT_DIR)/aot, ( \ + $$(FIXPATH) $(JDK_UNDER_TEST)/bin/java \ + $$($1_VM_OPTIONS) -Xlog:cds,cds+class=debug:file=$$($1_AOT_JDK_CACHE).log \ + -XX:AOTMode=create -XX:AOTConfiguration=$$($1_AOT_JDK_CONF) -XX:AOTCache=$$($1_AOT_JDK_CACHE) \ + )) + + $1_AOT_TARGETS += $$($1_AOT_JDK_CACHE) + +endef + + SetupRunJtregTest = $(NamedParamsMacroTemplate) define SetupRunJtregTestBody $1_TEST_RESULTS_DIR := $$(TEST_RESULTS_DIR)/$1 @@ -762,6 +815,7 @@ define SetupRunJtregTestBody JTREG_RETRY_COUNT ?= 0 JTREG_REPEAT_COUNT ?= 0 JTREG_REPORT ?= files + JTREG_AOT_JDK ?= false ifneq ($$(JTREG_RETRY_COUNT), 0) ifneq ($$(JTREG_REPEAT_COUNT), 0) @@ -891,6 +945,17 @@ define SetupRunJtregTestBody endif endif + ifeq ($$(JTREG_AOT_JDK), true) + $$(info Add AOT target for $1) + $$(eval $$(call SetupAot, $1, VM_OPTIONS := $$(JTREG_ALL_OPTIONS) )) + + $$(info AOT_TARGETS=$$($1_AOT_TARGETS)) + $$(info AOT_JDK_CACHE=$$($1_AOT_JDK_CACHE)) + + $1_JTREG_BASIC_OPTIONS += -vmoption:-XX:AOTCache="$$($1_AOT_JDK_CACHE)" + endif + + $$(eval $$(call SetupRunJtregTestCustom, $1)) # SetupRunJtregTestCustom might also adjust JTREG_AUTO_ variables @@ -906,6 +971,7 @@ define SetupRunJtregTestBody JTREG_TIMEOUT_FACTOR ?= $$(JTREG_AUTO_TIMEOUT_FACTOR) clean-outputdirs-$1: + $$(call LogWarn, Clean up dirs for $1) $$(RM) -r $$($1_TEST_SUPPORT_DIR) $$(RM) -r $$($1_TEST_RESULTS_DIR) @@ -953,7 +1019,7 @@ define SetupRunJtregTestBody done endif - run-test-$1: pre-run-test clean-outputdirs-$1 + run-test-$1: clean-outputdirs-$1 pre-run-test $$($1_AOT_TARGETS) $$(call LogWarn) $$(call LogWarn, Running test '$$($1_TEST)') $$(call MakeDir, $$($1_TEST_RESULTS_DIR) $$($1_TEST_SUPPORT_DIR) \ diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp new file mode 100644 index 00000000000..b09dfcde6b1 --- /dev/null +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" +#include "cds/archiveBuilder.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" +#include "classfile/vmSymbols.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "oops/symbol.hpp" +#include "runtime/javaCalls.hpp" + +// Detector for class names we wish to handle specially. +// It is either an exact string match or a string prefix match. +class AOTClassInitializer::AllowedSpec { + const char* _class_name; + bool _is_prefix; + int _len; +public: + AllowedSpec(const char* class_name, bool is_prefix = false) + : _class_name(class_name), _is_prefix(is_prefix) + { + _len = (class_name == nullptr) ? 0 : (int)strlen(class_name); + } + const char* class_name() { return _class_name; } + + bool matches(Symbol* name, int len) { + assert(_class_name != nullptr, "caller resp."); + if (_is_prefix) { + return len >= _len && name->starts_with(_class_name); + } else { + return len == _len && name->equals(_class_name); + } + } +}; + + +// Tell if ik has a name that matches one of the given specs. +bool AOTClassInitializer::is_allowed(AllowedSpec* specs, InstanceKlass* ik) { + Symbol* name = ik->name(); + int len = name->utf8_length(); + for (AllowedSpec* s = specs; s->class_name() != nullptr; s++) { + if (s->matches(name, len)) { + // If a type is included in the tables inside can_archive_initialized_mirror(), we require that + // - all super classes must be included + // - all super interfaces that have must be included. + // This ensures that in the production run, we don't run the of a supertype but skips + // ik's . + if (ik->java_super() != nullptr) { + DEBUG_ONLY(ResourceMark rm); + assert(AOTClassInitializer::can_archive_initialized_mirror(ik->java_super()), + "super class %s of %s must be aot-initialized", ik->java_super()->external_name(), + ik->external_name()); + } + + Array* interfaces = ik->local_interfaces(); + int len = interfaces->length(); + for (int i = 0; i < len; i++) { + InstanceKlass* intf = interfaces->at(i); + if (intf->class_initializer() != nullptr) { + assert(AOTClassInitializer::can_archive_initialized_mirror(intf), + "super interface %s (which has ) of %s must be aot-initialized", intf->external_name(), + ik->external_name()); + } + } + + return true; + } + } + return false; +} + + +bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { + assert(!ArchiveBuilder::current()->is_in_buffer_space(ik), "must be source klass"); + if (!CDSConfig::is_initing_classes_at_dump_time()) { + return false; + } + + if (!ik->is_initialized()) { + return false; + } + + if (ik->is_hidden()) { + return HeapShared::is_archivable_hidden_klass(ik); + } + + if (ik->is_enum_subclass()) { + return true; + } + + // About "static field that may hold a different value" errors: + // + // Automatic selection for aot-inited classes + // ========================================== + // + // When CDSConfig::is_initing_classes_at_dump_time() is enabled, + // HeapShared::find_all_aot_initialized_classes() 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 + // mirrors of these classes, and their methods are NOT executed + // at runtime. + // + // For example, with -XX:+AOTInvokeDynamicLinking, _run_time_special_subgraph + // will contain some DirectMethodHandle objects. As a result, the DirectMethodHandle + // class is automatically marked as aot-inited. + // + // When a class is aot-inited, its static fields are already set up + // by executing the method at AOT assembly time. Later on + // in the production run, when the class would normally be + // initialized, the VM performs guarding and synchronization as if + // it were going to run the again, but instead it simply + // observes that that class was aot-inited. The VM assumes that, if + // it were to run again, it would get a semantically + // equivalent set of final field values, so it just adopts the + // existing field values (from AOT assembly) and skips the call to + // . There may at that point be fixups performed by ad hoc + // code, if the VM recognizes a request in the library. + // + // It is true that this is not generally correct for all possible + // Java code. A method might have a side effect beyond + // initializing the static fields. It might send an email somewhere + // noting the current time of day. In that case, such an email + // would have been sent during the AOT assembly phase, and the email + // would NOT be sent again during production. This is clearly NOT + // what a user would want, if this were a general purpose facility. + // But in fact it is only for certain well-behaved classes, which + // are known NOT to have such side effects. We know this because + // the optimization (of skipping for aot-init classes) is + // only applied to classes fully defined by the JDK. + // + // (A day may come when we figure out how to gracefully extend this + // optimization to untrusted third parties, but it is not this day.) + // + // Manual selection + // ================ + // + // There are important cases where one aot-init class has a side + // effect on another aot-class, a side effect which is not captured + // in any static field value in either class. The simplest example + // is class A forces the initialization of class B. In that case, + // we need to aot-init either both classes or neither. From looking + // at the JDK state after AOT assembly is done, it is hard to tell + // that A "touched" B and B might escape our notice. Another common + // example is A copying a field value from B. We don't know where A + // got the value, but it would be wrong to re-initialize B at + // startup, while keeping the snapshot of the old B value in A. In + // general, if we aot-init A, we need to aot-init every class B that + // somehow contributed to A's initial state, and every class C that + // was somehow side-effected by A's initialization. We say that the + // aot-init of A is "init-coupled" to those of B and C. + // + // So there are init-coupled classes that cannot be automatically discovered. For + // example, DirectMethodHandle::IMPL_NAMES points to MethodHandles::IMPL_NAMES, + // but the MethodHandles class is not automatically marked because there are + // no archived instances of the MethodHandles type. + // + // If we aot-initialize DirectMethodHandle, but allow MethodHandles to be + // initialized at runtime, MethodHandles::IMPL_NAMES will get a different + // value than DirectMethodHandle::IMPL_NAMES. This *may or may not* be a problem, + // but to ensure compatibility, we should try to preserve the identity equality + // of these two fields. + // + // To do that, we add MethodHandles to the indy_specs[] table below. + // + // Luckily we do not need to be all-knowing in order to choose which + // items to add to that table. We have tools to help detect couplings. + // + // Automatic validation + // ==================== + // + // CDSHeapVerifier is used to detect potential problems with identity equality. + // + // A class B is assumed to be init-coupled to some aot-init class if + // B has a field which points to a live object X in the AOT heap. + // The live object X was created by some other class A which somehow + // used B's reference to X, perhaps with the help of an intermediate + // class Z. Or, B pulled the reference to X from some other class + // Y, and B obtained that reference from Y (or an intermediate Z). + // It is not certain how X got into the heap, nor whether B + // contributed it, but it is a good heuristic that B is init-coupled + // to X's class or some other aot-init class. In any case, B should + // be made an aot-init class as well, unless a manual inspection + // shows that would be a problem. If there is a problem, then the + // JDK code for B and/or X probably needs refactoring. If there is + // no problem, we add B to the list. Typically the same scan will + // find any other accomplices Y, Z, etc. One failure would be a + // class Q whose only initialization action is to scribble a special + // value into B, from which the value X is derived and then makes + // its way into the heap. In that case, the heuristic does not + // identify Q. It is (currently) a human responsibility, of JDK + // engineers, not to write such dirty JDK code, or to repair it if + // it crops up. Eventually we may have tools, or even a user mode + // with design rules and checks, that will vet our code base more + // automatically. + // + // To see how the tool detects the problem with MethodHandles::IMPL_NAMES: + // + // - Comment out all the lines in indy_specs[] except the {nullptr} line. + // - Rebuild the JDK + // + // Then run the following: + // java -XX:AOTMode=record -XX:AOTConfiguration=jc.aotconfig com.sun.tools.javac.Main + // java -XX:AOTMode=create -Xlog:cds -XX:AOTCache=jc.aot -XX:AOTConfiguration=jc.aotconfig + // + // You will see an error like this: + // + // Archive heap points to a static field that may hold a different value at runtime: + // Field: java/lang/invoke/MethodHandles::IMPL_NAMES + // Value: java.lang.invoke.MemberName$Factory + // {0x000000060e906ae8} - klass: 'java/lang/invoke/MemberName$Factory' - flags: + // + // - ---- fields (total size 2 words): + // --- trace begin --- + // [ 0] {0x000000060e8deeb0} java.lang.Class (java.lang.invoke.DirectMethodHandle::IMPL_NAMES) + // [ 1] {0x000000060e906ae8} java.lang.invoke.MemberName$Factory + // --- trace end --- + // + // Trouble-shooting + // ================ + // + // If you see a "static field that may hold a different value" error, it's probably + // because you've made some changes in the JDK core libraries (most likely + // java.lang.invoke). + // + // - Did you add a new static field to a class that could be referenced by + // cached object instances of MethodType, MethodHandle, etc? You may need + // to add that class to indy_specs[]. + // - Did you modify the of the classes in java.lang.invoke such that + // a static field now points to an object that should not be cached (e.g., + // a native resource such as a file descriptior, or a Thread)? + // + // Note that these potential problems only occur when one class gets + // the aot-init treatment, AND another class is init-coupled to it, + // AND the coupling is not detected. Currently there are a number + // classes that get the aot-init treatment, in java.lang.invoke + // because of invokedynamic. They are few enough for now to be + // manually tracked. There may be more in the future. + + // IS_PREFIX means that we match all class names that start with a + // prefix. Otherwise, it is an exact match, of just one class name. + const bool IS_PREFIX = true; + + { + static AllowedSpec specs[] = { + // everybody's favorite super + {"java/lang/Object"}, + + // above we selected all enums; we must include their super as well + {"java/lang/Enum"}, + {nullptr} + }; + if (is_allowed(specs, ik)) { + return true; + } + } + + if (CDSConfig::is_dumping_invokedynamic()) { + // This table was created with the help of CDSHeapVerifier. + // Also, some $Holder classes are needed. E.g., Invokers. explicitly + // initializes Invokers$Holder. Since Invokers. won't be executed + // at runtime, we need to make sure Invokers$Holder is also aot-inited. + // + // We hope we can reduce the size of this list over time, and move + // the responsibility for identifying such classes into the JDK + // code itself. See tracking RFE JDK-8342481. + static AllowedSpec indy_specs[] = { + {"java/lang/constant/ConstantDescs"}, + {"java/lang/constant/DynamicConstantDesc"}, + {"java/lang/invoke/BoundMethodHandle"}, + {"java/lang/invoke/BoundMethodHandle$Specializer"}, + {"java/lang/invoke/BoundMethodHandle$Species_", IS_PREFIX}, + {"java/lang/invoke/ClassSpecializer"}, + {"java/lang/invoke/ClassSpecializer$", IS_PREFIX}, + {"java/lang/invoke/DelegatingMethodHandle"}, + {"java/lang/invoke/DelegatingMethodHandle$Holder"}, // UNSAFE.ensureClassInitialized() + {"java/lang/invoke/DirectMethodHandle"}, + {"java/lang/invoke/DirectMethodHandle$Constructor"}, + {"java/lang/invoke/DirectMethodHandle$Holder"}, // UNSAFE.ensureClassInitialized() + {"java/lang/invoke/Invokers"}, + {"java/lang/invoke/Invokers$Holder"}, // UNSAFE.ensureClassInitialized() + {"java/lang/invoke/LambdaForm"}, + {"java/lang/invoke/LambdaForm$Holder"}, // UNSAFE.ensureClassInitialized() + {"java/lang/invoke/LambdaForm$NamedFunction"}, + {"java/lang/invoke/MethodHandle"}, + {"java/lang/invoke/MethodHandles"}, + {"java/lang/invoke/SimpleMethodHandle"}, + {"java/util/Collections"}, + {"java/util/stream/Collectors"}, + {"jdk/internal/constant/ConstantUtils"}, + {"jdk/internal/constant/PrimitiveClassDescImpl"}, + {"jdk/internal/constant/ReferenceClassDescImpl"}, + + // Can't include this, as it will pull in MethodHandleStatics which has many environment + // dependencies (on system properties, etc). + // MethodHandleStatics is an example of a class that must NOT get the aot-init treatment, + // because of its strong reliance on (a) final fields which are (b) environmentally determined. + //{"java/lang/invoke/InvokerBytecodeGenerator"}, + + {nullptr} + }; + if (is_allowed(indy_specs, ik)) { + return true; + } + } + + return false; +} + +// TODO: currently we have a hard-coded list. We should turn this into +// an annotation: @jdk.internal.vm.annotation.RuntimeSetupRequired +// See JDK-8342481. +bool AOTClassInitializer::is_runtime_setup_required(InstanceKlass* ik) { + return ik == vmClasses::Class_klass() || + ik == vmClasses::internal_Unsafe_klass() || + ik == vmClasses::ConcurrentHashMap_klass(); +} + +void AOTClassInitializer::call_runtime_setup(JavaThread* current, InstanceKlass* ik) { + assert(ik->has_aot_initialized_mirror(), "sanity"); + if (ik->is_runtime_setup_required()) { + if (log_is_enabled(Info, cds, init)) { + ResourceMark rm; + log_info(cds, init)("Calling %s::runtimeSetup()", ik->external_name()); + } + JavaValue result(T_VOID); + JavaCalls::call_static(&result, ik, + vmSymbols::runtimeSetup(), + vmSymbols::void_method_signature(), current); + if (current->has_pending_exception()) { + // We cannot continue, as we might have cached instances of ik in the heap, but propagating the + // exception would cause ik to be in an error state. + AOTLinkedClassBulkLoader::exit_on_exception(current); + } + } +} + diff --git a/src/hotspot/share/cds/aotClassInitializer.hpp b/src/hotspot/share/cds/aotClassInitializer.hpp new file mode 100644 index 00000000000..c8693a0add3 --- /dev/null +++ b/src/hotspot/share/cds/aotClassInitializer.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +#ifndef SHARE_CDS_AOTCLASSINITIALIZER_HPP +#define SHARE_CDS_AOTCLASSINITIALIZER_HPP + +#include "memory/allStatic.hpp" +#include "utilities/exceptions.hpp" + +class InstanceKlass; + +class AOTClassInitializer : AllStatic { + class AllowedSpec; + static bool is_allowed(AllowedSpec* specs, InstanceKlass* ik); + +public: + // Called by heapShared.cpp to see if src_ik->java_mirror() can be archived in + // the initialized state. + static bool can_archive_initialized_mirror(InstanceKlass* src_ik); + + static bool is_runtime_setup_required(InstanceKlass* ik); + static void call_runtime_setup(JavaThread* current, InstanceKlass* ik); +}; + +#endif // SHARE_CDS_AOTCLASSINITIALIZER_HPP diff --git a/src/hotspot/share/cds/aotClassLinker.cpp b/src/hotspot/share/cds/aotClassLinker.cpp new file mode 100644 index 00000000000..8525ce928a8 --- /dev/null +++ b/src/hotspot/share/cds/aotClassLinker.cpp @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotLinkedClassTable.hpp" +#include "cds/archiveBuilder.hpp" +#include "cds/archiveUtils.inline.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" +#include "cds/lambdaFormInvokers.inline.hpp" +#include "classfile/classLoader.hpp" +#include "classfile/dictionary.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "classfile/vmClasses.hpp" +#include "memory/resourceArea.hpp" +#include "oops/constantPool.inline.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.inline.hpp" +#include "runtime/handles.inline.hpp" + +AOTClassLinker::ClassesTable* AOTClassLinker::_vm_classes = nullptr; +AOTClassLinker::ClassesTable* AOTClassLinker::_candidates = nullptr; +GrowableArrayCHeap* AOTClassLinker::_sorted_candidates = nullptr; + +#ifdef ASSERT +bool AOTClassLinker::is_initialized() { + assert(CDSConfig::is_dumping_archive(), "AOTClassLinker is for CDS dumping only"); + return _vm_classes != nullptr; +} +#endif + +void AOTClassLinker::initialize() { + assert(!is_initialized(), "sanity"); + + _vm_classes = new (mtClass)ClassesTable(); + _candidates = new (mtClass)ClassesTable(); + _sorted_candidates = new GrowableArrayCHeap(1000); + + for (auto id : EnumRange{}) { + add_vm_class(vmClasses::klass_at(id)); + } + + assert(is_initialized(), "sanity"); + + AOTConstantPoolResolver::initialize(); +} + +void AOTClassLinker::dispose() { + assert(is_initialized(), "sanity"); + + delete _vm_classes; + delete _candidates; + delete _sorted_candidates; + _vm_classes = nullptr; + _candidates = nullptr; + _sorted_candidates = nullptr; + + assert(!is_initialized(), "sanity"); + + AOTConstantPoolResolver::dispose(); +} + +bool AOTClassLinker::is_vm_class(InstanceKlass* ik) { + assert(is_initialized(), "sanity"); + return (_vm_classes->get(ik) != nullptr); +} + +void AOTClassLinker::add_vm_class(InstanceKlass* ik) { + assert(is_initialized(), "sanity"); + bool created; + _vm_classes->put_if_absent(ik, &created); + if (created) { + if (CDSConfig::is_dumping_aot_linked_classes()) { + bool v = try_add_candidate(ik); + assert(v, "must succeed for VM class"); + } + InstanceKlass* super = ik->java_super(); + if (super != nullptr) { + add_vm_class(super); + } + Array* ifs = ik->local_interfaces(); + for (int i = 0; i < ifs->length(); i++) { + add_vm_class(ifs->at(i)); + } + } +} + +bool AOTClassLinker::is_candidate(InstanceKlass* ik) { + return (_candidates->get(ik) != nullptr); +} + +void AOTClassLinker::add_new_candidate(InstanceKlass* ik) { + assert(!is_candidate(ik), "caller need to check"); + _candidates->put_when_absent(ik, true); + _sorted_candidates->append(ik); + + if (log_is_enabled(Info, cds, aot, link)) { + ResourceMark rm; + log_info(cds, aot, link)("%s %s %p", class_category_name(ik), ik->external_name(), ik); + } +} + +// ik is a candidate for aot-linking; see if it can really work +// that way, and return success or failure. Not only must ik itself +// look like a class that can be aot-linked but its supers must also be +// aot-linkable. +bool AOTClassLinker::try_add_candidate(InstanceKlass* ik) { + assert(is_initialized(), "sanity"); + assert(CDSConfig::is_dumping_aot_linked_classes(), "sanity"); + + if (!SystemDictionaryShared::is_builtin(ik)) { + // not loaded by a class loader which we know about + return false; + } + + if (is_candidate(ik)) { // already checked. + return true; + } + + if (ik->is_hidden()) { + assert(ik->shared_class_loader_type() != ClassLoader::OTHER, "must have been set"); + if (!CDSConfig::is_dumping_invokedynamic()) { + return false; + } + if (!SystemDictionaryShared::should_hidden_class_be_archived(ik)) { + return false; + } + if (HeapShared::is_lambda_proxy_klass(ik)) { + InstanceKlass* nest_host = ik->nest_host_not_null(); + if (!try_add_candidate(nest_host)) { + ResourceMark rm; + log_warning(cds, aot, link)("%s cannot be aot-linked because it nest host is not aot-linked", ik->external_name()); + return false; + } + } + } + + InstanceKlass* s = ik->java_super(); + if (s != nullptr && !try_add_candidate(s)) { + return false; + } + + Array* interfaces = ik->local_interfaces(); + int num_interfaces = interfaces->length(); + for (int index = 0; index < num_interfaces; index++) { + InstanceKlass* intf = interfaces->at(index); + if (!try_add_candidate(intf)) { + return false; + } + } + + // There are no loops in the class hierarchy, and this function is always called single-threaded, so + // we know ik has not been added yet. + assert(CDSConfig::current_thread_is_vm_or_dumper(), "that's why we don't need locks"); + add_new_candidate(ik); + + return true; +} + +void AOTClassLinker::add_candidates() { + assert_at_safepoint(); + if (CDSConfig::is_dumping_aot_linked_classes()) { + GrowableArray* klasses = ArchiveBuilder::current()->klasses(); + for (GrowableArrayIterator it = klasses->begin(); it != klasses->end(); ++it) { + Klass* k = *it; + if (k->is_instance_klass()) { + try_add_candidate(InstanceKlass::cast(k)); + } + } + } +} + +void AOTClassLinker::write_to_archive() { + assert(is_initialized(), "sanity"); + assert_at_safepoint(); + + if (CDSConfig::is_dumping_aot_linked_classes()) { + AOTLinkedClassTable* table = AOTLinkedClassTable::get(CDSConfig::is_dumping_static_archive()); + table->set_boot(write_classes(nullptr, true)); + table->set_boot2(write_classes(nullptr, false)); + table->set_platform(write_classes(SystemDictionary::java_platform_loader(), false)); + table->set_app(write_classes(SystemDictionary::java_system_loader(), false)); + } +} + +Array* AOTClassLinker::write_classes(oop class_loader, bool is_javabase) { + ResourceMark rm; + GrowableArray list; + + for (int i = 0; i < _sorted_candidates->length(); i++) { + InstanceKlass* ik = _sorted_candidates->at(i); + if (ik->class_loader() != class_loader) { + continue; + } + if ((ik->module() == ModuleEntryTable::javabase_moduleEntry()) != is_javabase) { + continue; + } + + if (ik->is_shared() && CDSConfig::is_dumping_dynamic_archive()) { + if (CDSConfig::is_using_aot_linked_classes()) { + // This class was recorded as AOT-linked for the base archive, + // so there's no need to do so again for the dynamic archive. + } else { + list.append(ik); + } + } else { + list.append(ArchiveBuilder::current()->get_buffered_addr(ik)); + } + } + + if (list.length() == 0) { + return nullptr; + } else { + const char* category = class_category_name(list.at(0)); + log_info(cds, aot, link)("wrote %d class(es) for category %s", list.length(), category); + return ArchiveUtils::archive_array(&list); + } +} + +int AOTClassLinker::num_platform_initiated_classes() { + if (CDSConfig::is_dumping_aot_linked_classes()) { + // AOTLinkedClassBulkLoader will initiate loading of all public boot classes in the platform loader. + return count_public_classes(nullptr); + } else { + return 0; + } +} + +int AOTClassLinker::num_app_initiated_classes() { + if (CDSConfig::is_dumping_aot_linked_classes()) { + // AOTLinkedClassBulkLoader will initiate loading of all public boot/platform classes in the app loader. + return count_public_classes(nullptr) + count_public_classes(SystemDictionary::java_platform_loader()); + } else { + return 0; + } +} + +int AOTClassLinker::count_public_classes(oop loader) { + int n = 0; + for (int i = 0; i < _sorted_candidates->length(); i++) { + InstanceKlass* ik = _sorted_candidates->at(i); + if (ik->is_public() && !ik->is_hidden() && ik->class_loader() == loader) { + n++; + } + } + + return n; +} + +// Used in logging: "boot1", "boot2", "plat", "app" and "unreg", or "array" +const char* AOTClassLinker::class_category_name(Klass* k) { + if (ArchiveBuilder::is_active() && ArchiveBuilder::current()->is_in_buffer_space(k)) { + k = ArchiveBuilder::current()->get_source_addr(k); + } + + if (k->is_array_klass()) { + return "array"; + } else { + oop loader = k->class_loader(); + if (loader == nullptr) { + if (k->module() != nullptr && + k->module()->name() != nullptr && + k->module()->name()->equals("java.base")) { + return "boot1"; // boot classes in java.base are loaded in the 1st phase + } else { + return "boot2"; // boot classes outside of java.base are loaded in the 2nd phase phase + } + } else { + if (loader == SystemDictionary::java_platform_loader()) { + return "plat"; + } else if (loader == SystemDictionary::java_system_loader()) { + return "app"; + } else { + return "unreg"; + } + } + } +} + +const char* AOTClassLinker::class_category_name(AOTLinkedClassCategory category) { + switch (category) { + case AOTLinkedClassCategory::BOOT1: + return "boot1"; + case AOTLinkedClassCategory::BOOT2: + return "boot2"; + case AOTLinkedClassCategory::PLATFORM: + return "plat"; + case AOTLinkedClassCategory::APP: + return "app"; + case AOTLinkedClassCategory::UNREGISTERED: + default: + return "unreg"; + } +} + diff --git a/src/hotspot/share/cds/aotClassLinker.hpp b/src/hotspot/share/cds/aotClassLinker.hpp new file mode 100644 index 00000000000..f15684a1ad2 --- /dev/null +++ b/src/hotspot/share/cds/aotClassLinker.hpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +#ifndef SHARE_CDS_AOTCLASSLINKER_HPP +#define SHARE_CDS_AOTCLASSLINKER_HPP + +#include "interpreter/bytecodes.hpp" +#include "memory/allocation.hpp" +#include "memory/allStatic.hpp" +#include "oops/oopsHierarchy.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/macros.hpp" +#include "utilities/resourceHash.hpp" + +class AOTLinkedClassTable; +class InstanceKlass; +class SerializeClosure; +template class Array; +enum class AOTLinkedClassCategory : int; + +// AOTClassLinker is used during the AOTCache Assembly Phase. +// It links eligible classes before they are written into the AOTCache +// +// The classes linked by AOTClassLinker are recorded in an AOTLinkedClassTable, +// which is also written into the AOTCache. +// +// AOTClassLinker is enabled by the -XX:+AOTClassLinking option. If this option +// is disabled, an empty AOTLinkedClassTable will be included in the AOTCache. +// +// For each class C in the AOTLinkedClassTable, the following properties for C +// are assigned by AOTClassLinker and cannot be changed thereafter. +// - The CodeSource for C +// - The bytecodes in C +// - The supertypes of C +// - The ClassLoader, Package and Module of C +// - The visibility of C +// +// During a production run, the JVM can use an AOTCache with an AOTLinkedClassTable +// only if it's guaranteed to produce the same results for the above set of properties +// for each class C in the AOTLinkedClassTable. +// +// For example, +// - C may be loaded from a different CodeSource when the CLASSPATH is changed. +// - Some JVMTI agent may allow the bytecodes of C to be modified. +// - C may be made invisible by module options such as --add-modules +// In such situations, the JVM will refuse to load the AOTCache. +// +class AOTClassLinker : AllStatic { + static const int TABLE_SIZE = 15889; // prime number + using ClassesTable = ResourceHashtable; + + // Classes loaded inside vmClasses::resolve_all() + static ClassesTable* _vm_classes; + + // Classes that should be automatically loaded into system dictionary at VM start-up + static ClassesTable* _candidates; + + // Sorted list such that super types come first. + static GrowableArrayCHeap* _sorted_candidates; + + DEBUG_ONLY(static bool is_initialized()); + + static void add_vm_class(InstanceKlass* ik); + static void add_new_candidate(InstanceKlass* ik); + + static Array* write_classes(oop class_loader, bool is_javabase); + static int count_public_classes(oop loader); + +public: + static void initialize(); + static void add_candidates(); + static void write_to_archive(); + static void dispose(); + + // Is this class resolved as part of vmClasses::resolve_all()? + static bool is_vm_class(InstanceKlass* ik); + + // When CDS is enabled, is ik guaranteed to be linked at deployment time (and + // cannot be replaced by JVMTI, etc)? + // This is a necessary (but not sufficient) condition for keeping a direct pointer + // to ik in AOT-computed data (such as ConstantPool entries in archived classes, + // or in AOT-compiled code). + static bool is_candidate(InstanceKlass* ik); + + // Request that ik be added to the candidates table. This will return true only if + // ik is allowed to be aot-linked. + static bool try_add_candidate(InstanceKlass* ik); + + static int num_app_initiated_classes(); + static int num_platform_initiated_classes(); + + // Used in logging: "boot1", "boot2", "plat", "app" and "unreg"; + static const char* class_category_name(AOTLinkedClassCategory category); + static const char* class_category_name(Klass* k); +}; + +// AOT-linked classes are divided into different categories and are loaded +// in two phases during the production run. +enum class AOTLinkedClassCategory : int { + BOOT1, // Only java.base classes are loaded in the 1st phase + BOOT2, // All boot classes that not in java.base are loaded in the 2nd phase + PLATFORM, // Classes for platform loader, loaded in the 2nd phase + APP, // Classes for the app loader, loaded in the 2nd phase + UNREGISTERED // classes loaded outside of the boot/platform/app loaders; currently not supported by AOTClassLinker +}; + +#endif // SHARE_CDS_AOTCLASSLINKER_HPP diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp new file mode 100644 index 00000000000..cc500557c8d --- /dev/null +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -0,0 +1,573 @@ +/* + * Copyright (c) 2022, 2024, 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotConstantPoolResolver.hpp" +#include "cds/archiveBuilder.hpp" +#include "cds/cdsConfig.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "classfile/vmClasses.hpp" +#include "interpreter/bytecodeStream.hpp" +#include "interpreter/interpreterRuntime.hpp" +#include "memory/resourceArea.hpp" +#include "oops/constantPool.inline.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.inline.hpp" +#include "runtime/handles.inline.hpp" + +AOTConstantPoolResolver::ClassesTable* AOTConstantPoolResolver::_processed_classes = nullptr; + +void AOTConstantPoolResolver::initialize() { + assert(_processed_classes == nullptr, "must be"); + _processed_classes = new (mtClass)ClassesTable(); +} + +void AOTConstantPoolResolver::dispose() { + assert(_processed_classes != nullptr, "must be"); + delete _processed_classes; + _processed_classes = nullptr; +} + +// Returns true if we CAN PROVE that cp_index will always resolve to +// the same information at both dump time and run time. This is a +// necessary (but not sufficient) condition for pre-resolving cp_index +// during CDS archive assembly. +bool AOTConstantPoolResolver::is_resolution_deterministic(ConstantPool* cp, int cp_index) { + assert(!is_in_archivebuilder_buffer(cp), "sanity"); + + if (cp->tag_at(cp_index).is_klass()) { + // We require cp_index to be already resolved. This is fine for now, are we + // currently archive only CP entries that are already resolved. + Klass* resolved_klass = cp->resolved_klass_at(cp_index); + return resolved_klass != nullptr && is_class_resolution_deterministic(cp->pool_holder(), resolved_klass); + } else if (cp->tag_at(cp_index).is_invoke_dynamic()) { + return is_indy_resolution_deterministic(cp, cp_index); + } else if (cp->tag_at(cp_index).is_field() || + cp->tag_at(cp_index).is_method() || + cp->tag_at(cp_index).is_interface_method()) { + int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); + if (!cp->tag_at(klass_cp_index).is_klass()) { + // Not yet resolved + return false; + } + Klass* k = cp->resolved_klass_at(klass_cp_index); + if (!is_class_resolution_deterministic(cp->pool_holder(), k)) { + return false; + } + + if (!k->is_instance_klass()) { + // TODO: support non instance klasses as well. + return false; + } + + // Here, We don't check if this entry can actually be resolved to a valid Field/Method. + // This method should be called by the ConstantPool to check Fields/Methods that + // have already been successfully resolved. + return true; + } else { + return false; + } +} + +bool AOTConstantPoolResolver::is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class) { + assert(!is_in_archivebuilder_buffer(cp_holder), "sanity"); + assert(!is_in_archivebuilder_buffer(resolved_class), "sanity"); + + if (resolved_class->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(resolved_class); + + if (!ik->is_shared() && SystemDictionaryShared::is_excluded_class(ik)) { + return false; + } + + if (cp_holder->is_subtype_of(ik)) { + // All super types of ik will be resolved in ik->class_loader() before + // ik is defined in this loader, so it's safe to archive the resolved klass reference. + return true; + } + + if (CDSConfig::is_dumping_aot_linked_classes()) { + // Need to call try_add_candidate instead of is_candidate, as this may be called + // before AOTClassLinker::add_candidates(). + if (AOTClassLinker::try_add_candidate(ik)) { + return true; + } else { + return false; + } + } else if (AOTClassLinker::is_vm_class(ik)) { + if (ik->class_loader() != cp_holder->class_loader()) { + // At runtime, cp_holder() may not be able to resolve to the same + // ik. For example, a different version of ik may be defined in + // cp->pool_holder()'s loader using MethodHandles.Lookup.defineClass(). + return false; + } else { + return true; + } + } else { + return false; + } + } else if (resolved_class->is_objArray_klass()) { + Klass* elem = ObjArrayKlass::cast(resolved_class)->bottom_klass(); + if (elem->is_instance_klass()) { + return is_class_resolution_deterministic(cp_holder, InstanceKlass::cast(elem)); + } else if (elem->is_typeArray_klass()) { + return true; + } else { + return false; + } + } else if (resolved_class->is_typeArray_klass()) { + return true; + } else { + return false; + } +} + +void AOTConstantPoolResolver::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { + if (!ik->is_linked()) { + return; + } + bool first_time; + _processed_classes->put_if_absent(ik, &first_time); + if (!first_time) { + // We have already resolved the constants in class, so no need to do it again. + return; + } + + constantPoolHandle cp(THREAD, ik->constants()); + for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused + switch (cp->tag_at(cp_index).value()) { + case JVM_CONSTANT_String: + resolve_string(cp, cp_index, CHECK); // may throw OOM when interning strings. + break; + } + } +} + +// This works only for the boot/platform/app loaders +Klass* AOTConstantPoolResolver::find_loaded_class(Thread* current, oop class_loader, Symbol* name) { + HandleMark hm(current); + Handle h_loader(current, class_loader); + Klass* k = SystemDictionary::find_instance_or_array_klass(current, name, + h_loader, + Handle()); + if (k != nullptr) { + return k; + } + if (h_loader() == SystemDictionary::java_system_loader()) { + return find_loaded_class(current, SystemDictionary::java_platform_loader(), name); + } else if (h_loader() == SystemDictionary::java_platform_loader()) { + return find_loaded_class(current, nullptr, name); + } else { + assert(h_loader() == nullptr, "This function only works for boot/platform/app loaders %p %p %p", + cast_from_oop
(h_loader()), + cast_from_oop
(SystemDictionary::java_system_loader()), + cast_from_oop
(SystemDictionary::java_platform_loader())); + } + + return nullptr; +} + +Klass* AOTConstantPoolResolver::find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index) { + Symbol* name = cp->klass_name_at(class_cp_index); + return find_loaded_class(current, cp->pool_holder()->class_loader(), name); +} + +#if INCLUDE_CDS_JAVA_HEAP +void AOTConstantPoolResolver::resolve_string(constantPoolHandle cp, int cp_index, TRAPS) { + if (CDSConfig::is_dumping_heap()) { + int cache_index = cp->cp_to_object_index(cp_index); + ConstantPool::string_at_impl(cp, cp_index, cache_index, CHECK); + } +} +#endif + +void AOTConstantPoolResolver::preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { + if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data())) { + return; + } + + JavaThread* THREAD = current; + constantPoolHandle cp(THREAD, ik->constants()); + for (int cp_index = 1; cp_index < cp->length(); cp_index++) { + if (cp->tag_at(cp_index).value() == JVM_CONSTANT_UnresolvedClass) { + if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) { + // This class was not resolved during trial run. Don't attempt to resolve it. Otherwise + // the compiler may generate less efficient code. + continue; + } + if (find_loaded_class(current, cp(), cp_index) == nullptr) { + // Do not resolve any class that has not been loaded yet + continue; + } + Klass* resolved_klass = cp->klass_at(cp_index, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } else { + log_trace(cds, resolve)("Resolved class [%3d] %s -> %s", cp_index, ik->external_name(), + resolved_klass->external_name()); + } + } + } +} + +void AOTConstantPoolResolver::preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { + JavaThread* THREAD = current; + constantPoolHandle cp(THREAD, ik->constants()); + if (cp->cache() == nullptr) { + return; + } + for (int i = 0; i < ik->methods()->length(); i++) { + Method* m = ik->methods()->at(i); + BytecodeStream bcs(methodHandle(THREAD, m)); + while (!bcs.is_last_bytecode()) { + bcs.next(); + Bytecodes::Code raw_bc = bcs.raw_code(); + switch (raw_bc) { + case Bytecodes::_getfield: + case Bytecodes::_putfield: + maybe_resolve_fmi_ref(ik, m, raw_bc, bcs.get_index_u2(), preresolve_list, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } + break; + case Bytecodes::_invokehandle: + case Bytecodes::_invokespecial: + case Bytecodes::_invokevirtual: + case Bytecodes::_invokeinterface: + maybe_resolve_fmi_ref(ik, m, raw_bc, bcs.get_index_u2(), preresolve_list, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } + break; + default: + break; + } + } + } +} + +void AOTConstantPoolResolver::maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, + GrowableArray* preresolve_list, TRAPS) { + methodHandle mh(THREAD, m); + constantPoolHandle cp(THREAD, ik->constants()); + HandleMark hm(THREAD); + int cp_index = cp->to_cp_index(raw_index, bc); + + if (cp->is_resolved(raw_index, bc)) { + return; + } + + if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) { + // This field wasn't resolved during the trial run. Don't attempt to resolve it. Otherwise + // the compiler may generate less efficient code. + return; + } + + int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); + if (find_loaded_class(THREAD, cp(), klass_cp_index) == nullptr) { + // Do not resolve any field/methods from a class that has not been loaded yet. + return; + } + + Klass* resolved_klass = cp->klass_ref_at(raw_index, bc, CHECK); + + switch (bc) { + case Bytecodes::_getfield: + case Bytecodes::_putfield: + InterpreterRuntime::resolve_get_put(bc, raw_index, mh, cp, false /*initialize_holder*/, CHECK); + break; + + case Bytecodes::_invokevirtual: + case Bytecodes::_invokespecial: + case Bytecodes::_invokeinterface: + InterpreterRuntime::cds_resolve_invoke(bc, raw_index, cp, CHECK); + break; + + case Bytecodes::_invokehandle: + InterpreterRuntime::cds_resolve_invokehandle(raw_index, cp, CHECK); + break; + + default: + ShouldNotReachHere(); + } + + if (log_is_enabled(Trace, cds, resolve)) { + ResourceMark rm(THREAD); + bool resolved = cp->is_resolved(raw_index, bc); + Symbol* name = cp->name_ref_at(raw_index, bc); + Symbol* signature = cp->signature_ref_at(raw_index, bc); + log_trace(cds, resolve)("%s %s [%3d] %s -> %s.%s:%s", + (resolved ? "Resolved" : "Failed to resolve"), + Bytecodes::name(bc), cp_index, ik->external_name(), + resolved_klass->external_name(), + name->as_C_string(), signature->as_C_string()); + } +} + +void AOTConstantPoolResolver::preresolve_indy_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { + JavaThread* THREAD = current; + constantPoolHandle cp(THREAD, ik->constants()); + if (!CDSConfig::is_dumping_invokedynamic() || cp->cache() == nullptr) { + return; + } + + assert(preresolve_list != nullptr, "preresolve_indy_cp_entries() should not be called for " + "regenerated LambdaForm Invoker classes, which should not have indys anyway."); + + Array* indy_entries = cp->cache()->resolved_indy_entries(); + for (int i = 0; i < indy_entries->length(); i++) { + ResolvedIndyEntry* rie = indy_entries->adr_at(i); + int cp_index = rie->constant_pool_index(); + if (preresolve_list->at(cp_index) == true) { + if (!rie->is_resolved() && is_indy_resolution_deterministic(cp(), cp_index)) { + InterpreterRuntime::cds_resolve_invokedynamic(i, cp, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } + } + if (log_is_enabled(Trace, cds, resolve)) { + ResourceMark rm(THREAD); + log_trace(cds, resolve)("%s indy [%3d] %s", + rie->is_resolved() ? "Resolved" : "Failed to resolve", + cp_index, ik->external_name()); + } + } + } +} + +// Check the MethodType signatures used by parameters to the indy BSMs. Make sure we don't +// use types that have been excluded, or else we might end up creating MethodTypes that cannot be stored +// in the AOT cache. +bool AOTConstantPoolResolver::check_methodtype_signature(ConstantPool* cp, Symbol* sig, Klass** return_type_ret) { + ResourceMark rm; + for (SignatureStream ss(sig); !ss.is_done(); ss.next()) { + if (ss.is_reference()) { + Symbol* type = ss.as_symbol(); + Klass* k = find_loaded_class(Thread::current(), cp->pool_holder()->class_loader(), type); + if (k == nullptr) { + return false; + } + + if (SystemDictionaryShared::should_be_excluded(k)) { + if (log_is_enabled(Warning, cds, resolve)) { + ResourceMark rm; + log_warning(cds, resolve)("Cannot aot-resolve Lambda proxy because %s is excluded", k->external_name()); + } + return false; + } + + if (ss.at_return_type() && return_type_ret != nullptr) { + *return_type_ret = k; + } + } + } + return true; +} + +bool AOTConstantPoolResolver::check_lambda_metafactory_signature(ConstantPool* cp, Symbol* sig) { + Klass* k; + if (!check_methodtype_signature(cp, sig, &k)) { + return false; + } + + // is the interface type implemented by the lambda proxy + if (!k->is_interface()) { + // cp->pool_holder() doesn't look like a valid class generated by javac + return false; + } + + + // The linked lambda callsite has an instance of the interface implemented by this lambda. If this + // interface requires its to be executed, then we must delay the execution to the production run + // as can have side effects ==> exclude such cases. + InstanceKlass* intf = InstanceKlass::cast(k); + bool exclude = intf->interface_needs_clinit_execution_as_super(); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("%s aot-resolve Lambda proxy of interface type %s", + exclude ? "Cannot" : "Can", k->external_name()); + } + return !exclude; +} + +bool AOTConstantPoolResolver::check_lambda_metafactory_methodtype_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { + int mt_index = cp->operand_argument_index_at(bsms_attribute_index, arg_i); + if (!cp->tag_at(mt_index).is_method_type()) { + // malformed class? + return false; + } + + Symbol* sig = cp->method_type_signature_at(mt_index); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("Checking MethodType for LambdaMetafactory BSM arg %d: %s", arg_i, sig->as_C_string()); + } + + return check_methodtype_signature(cp, sig); +} + +bool AOTConstantPoolResolver::check_lambda_metafactory_methodhandle_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { + int mh_index = cp->operand_argument_index_at(bsms_attribute_index, arg_i); + if (!cp->tag_at(mh_index).is_method_handle()) { + // malformed class? + return false; + } + + Symbol* sig = cp->method_handle_signature_ref_at(mh_index); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("Checking MethodType of MethodHandle for LambdaMetafactory BSM arg %d: %s", arg_i, sig->as_C_string()); + } + return check_methodtype_signature(cp, sig); +} + +bool AOTConstantPoolResolver::is_indy_resolution_deterministic(ConstantPool* cp, int cp_index) { + assert(cp->tag_at(cp_index).is_invoke_dynamic(), "sanity"); + if (!CDSConfig::is_dumping_invokedynamic()) { + return false; + } + + InstanceKlass* pool_holder = cp->pool_holder(); + if (!SystemDictionaryShared::is_builtin(pool_holder)) { + return false; + } + + int bsm = cp->bootstrap_method_ref_index_at(cp_index); + int bsm_ref = cp->method_handle_index_at(bsm); + Symbol* bsm_name = cp->uncached_name_ref_at(bsm_ref); + Symbol* bsm_signature = cp->uncached_signature_ref_at(bsm_ref); + Symbol* bsm_klass = cp->klass_name_at(cp->uncached_klass_ref_index_at(bsm_ref)); + + // We currently support only StringConcatFactory::makeConcatWithConstants() and LambdaMetafactory::metafactory() + // We should mark the allowed BSMs in the JDK code using a private annotation. + // See notes on RFE JDK-8342481. + + if (bsm_klass->equals("java/lang/invoke/StringConcatFactory") && + bsm_name->equals("makeConcatWithConstants") && + bsm_signature->equals("(Ljava/lang/invoke/MethodHandles$Lookup;" + "Ljava/lang/String;" + "Ljava/lang/invoke/MethodType;" + "Ljava/lang/String;" + "[Ljava/lang/Object;" + ")Ljava/lang/invoke/CallSite;")) { + Symbol* factory_type_sig = cp->uncached_signature_ref_at(cp_index); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("Checking StringConcatFactory callsite signature [%d]: %s", cp_index, factory_type_sig->as_C_string()); + } + + Klass* k; + if (!check_methodtype_signature(cp, factory_type_sig, &k)) { + return false; + } + if (k != vmClasses::String_klass()) { + // bad class file? + return false; + } + + return true; + } + + if (bsm_klass->equals("java/lang/invoke/LambdaMetafactory") && + bsm_name->equals("metafactory") && + bsm_signature->equals("(Ljava/lang/invoke/MethodHandles$Lookup;" + "Ljava/lang/String;" + "Ljava/lang/invoke/MethodType;" + "Ljava/lang/invoke/MethodType;" + "Ljava/lang/invoke/MethodHandle;" + "Ljava/lang/invoke/MethodType;" + ")Ljava/lang/invoke/CallSite;")) { + /* + * An indy callsite is associated with the following MethodType and MethodHandles: + * + * https://github.com/openjdk/jdk/blob/580eb62dc097efeb51c76b095c1404106859b673/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java#L293-L309 + * + * MethodType factoryType The expected signature of the {@code CallSite}. The + * parameter types represent the types of capture variables; + * the return type is the interface to implement. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * + * MethodType interfaceMethodType Signature and return type of method to be + * implemented by the function object. + * + * MethodHandle implementation A direct method handle describing the implementation + * method which should be called (with suitable adaptation + * of argument types and return types, and with captured + * arguments prepended to the invocation arguments) at + * invocation time. + * + * MethodType dynamicMethodType The signature and return type that should + * be enforced dynamically at invocation time. + * In simple use cases this is the same as + * {@code interfaceMethodType}. + */ + Symbol* factory_type_sig = cp->uncached_signature_ref_at(cp_index); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("Checking indy callsite signature [%d]: %s", cp_index, factory_type_sig->as_C_string()); + } + + if (!check_lambda_metafactory_signature(cp, factory_type_sig)) { + return false; + } + + int bsms_attribute_index = cp->bootstrap_methods_attribute_index(cp_index); + int arg_count = cp->operand_argument_count_at(bsms_attribute_index); + if (arg_count != 3) { + // Malformed class? + return false; + } + + // interfaceMethodType + if (!check_lambda_metafactory_methodtype_arg(cp, bsms_attribute_index, 0)) { + return false; + } + + // implementation + if (!check_lambda_metafactory_methodhandle_arg(cp, bsms_attribute_index, 1)) { + return false; + } + + // dynamicMethodType + if (!check_lambda_metafactory_methodtype_arg(cp, bsms_attribute_index, 2)) { + return false; + } + + return true; + } + + return false; +} +#ifdef ASSERT +bool AOTConstantPoolResolver::is_in_archivebuilder_buffer(address p) { + if (!Thread::current()->is_VM_thread() || ArchiveBuilder::current() == nullptr) { + return false; + } else { + return ArchiveBuilder::current()->is_in_buffer_space(p); + } +} +#endif diff --git a/src/hotspot/share/cds/classPrelinker.hpp b/src/hotspot/share/cds/aotConstantPoolResolver.hpp similarity index 73% rename from src/hotspot/share/cds/classPrelinker.hpp rename to src/hotspot/share/cds/aotConstantPoolResolver.hpp index 41588961d8b..2f65849fbb4 100644 --- a/src/hotspot/share/cds/classPrelinker.hpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.hpp @@ -22,13 +22,13 @@ * */ -#ifndef SHARE_CDS_CLASSPRELINKER_HPP -#define SHARE_CDS_CLASSPRELINKER_HPP +#ifndef SHARE_CDS_AOTCONSTANTPOOLRESOLVER_HPP +#define SHARE_CDS_AOTCONSTANTPOOLRESOLVER_HPP #include "interpreter/bytecodes.hpp" -#include "oops/oopsHierarchy.hpp" #include "memory/allStatic.hpp" #include "memory/allocation.hpp" +#include "oops/oopsHierarchy.hpp" #include "runtime/handles.hpp" #include "utilities/exceptions.hpp" #include "utilities/macros.hpp" @@ -39,7 +39,9 @@ class constantPoolHandle; class InstanceKlass; class Klass; -// ClassPrelinker is used to perform ahead-of-time linking of ConstantPool entries +template class GrowableArray; + +// AOTConstantPoolResolver is used to perform ahead-of-time linking of ConstantPool entries // for archived InstanceKlasses. // // At run time, Java classes are loaded dynamically and may be replaced with JVMTI. @@ -49,23 +51,21 @@ class Klass; // For example, a JVM_CONSTANT_Class reference to a supertype can be safely resolved // at dump time, because at run time we will load a class from the CDS archive only // if all of its supertypes are loaded from the CDS archive. -class ClassPrelinker : AllStatic { - using ClassesTable = ResourceHashtable ; +class AOTConstantPoolResolver : AllStatic { + static const int TABLE_SIZE = 15889; // prime number + using ClassesTable = ResourceHashtable ; static ClassesTable* _processed_classes; - static ClassesTable* _vm_classes; - - static void add_one_vm_class(InstanceKlass* ik); #ifdef ASSERT + template static bool is_in_archivebuilder_buffer(T p) { + return is_in_archivebuilder_buffer((address)(p)); + } static bool is_in_archivebuilder_buffer(address p); #endif - template - static bool is_in_archivebuilder_buffer(T p) { - return is_in_archivebuilder_buffer((address)(p)); - } static void resolve_string(constantPoolHandle cp, int cp_index, TRAPS) NOT_CDS_JAVA_HEAP_RETURN; static bool is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class); + static bool is_indy_resolution_deterministic(ConstantPool* cp, int cp_index); static Klass* find_loaded_class(Thread* current, oop class_loader, Symbol* name); static Klass* find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index); @@ -73,18 +73,20 @@ class ClassPrelinker : AllStatic { // fmi = FieldRef/MethodRef/InterfaceMethodRef static void maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, GrowableArray* resolve_fmi_list, TRAPS); + + static bool check_methodtype_signature(ConstantPool* cp, Symbol* sig, Klass** return_type_ret = nullptr); + static bool check_lambda_metafactory_signature(ConstantPool* cp, Symbol* sig); + static bool check_lambda_metafactory_methodtype_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i); + static bool check_lambda_metafactory_methodhandle_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i); + public: static void initialize(); static void dispose(); static void preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); static void preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); + static void preresolve_indy_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); - // Is this class resolved as part of vmClasses::resolve_all()? If so, these - // classes are guatanteed to be loaded at runtime (and cannot be replaced by JVMTI) - // when CDS is enabled. Therefore, we can safely keep a direct reference to these - // classes. - static bool is_vm_class(InstanceKlass* ik); // Resolve all constant pool entries that are safe to be stored in the // CDS archive. @@ -93,4 +95,4 @@ public: static bool is_resolution_deterministic(ConstantPool* cp, int cp_index); }; -#endif // SHARE_CDS_CLASSPRELINKER_HPP +#endif // SHARE_CDS_AOTCONSTANTPOOLRESOLVER_HPP diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp new file mode 100644 index 00000000000..d823db9e8d7 --- /dev/null +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" +#include "cds/aotLinkedClassTable.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" +#include "classfile/classLoaderData.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "classfile/vmClasses.hpp" +#include "gc/shared/gcVMOperations.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/java.hpp" + +bool AOTLinkedClassBulkLoader::_boot2_completed = false; +bool AOTLinkedClassBulkLoader::_platform_completed = false; +bool AOTLinkedClassBulkLoader::_app_completed = false; +bool AOTLinkedClassBulkLoader::_all_completed = false; + +void AOTLinkedClassBulkLoader::serialize(SerializeClosure* soc, bool is_static_archive) { + AOTLinkedClassTable::get(is_static_archive)->serialize(soc); +} + +void AOTLinkedClassBulkLoader::load_javabase_classes(JavaThread* current) { + assert(CDSConfig::is_using_aot_linked_classes(), "sanity"); + load_classes_in_loader(current, AOTLinkedClassCategory::BOOT1, nullptr); // only java.base classes +} + +void AOTLinkedClassBulkLoader::load_non_javabase_classes(JavaThread* current) { + assert(CDSConfig::is_using_aot_linked_classes(), "sanity"); + + // is_using_aot_linked_classes() requires is_using_full_module_graph(). As a result, + // the platform/system class loader should already have been initialized as part + // of the FMG support. + assert(CDSConfig::is_using_full_module_graph(), "must be"); + assert(SystemDictionary::java_platform_loader() != nullptr, "must be"); + assert(SystemDictionary::java_system_loader() != nullptr, "must be"); + + load_classes_in_loader(current, AOTLinkedClassCategory::BOOT2, nullptr); // all boot classes outside of java.base + _boot2_completed = true; + + load_classes_in_loader(current, AOTLinkedClassCategory::PLATFORM, SystemDictionary::java_platform_loader()); + _platform_completed = true; + + load_classes_in_loader(current, AOTLinkedClassCategory::APP, SystemDictionary::java_system_loader()); + _app_completed = true; + _all_completed = true; +} + +void AOTLinkedClassBulkLoader::load_classes_in_loader(JavaThread* current, AOTLinkedClassCategory class_category, oop class_loader_oop) { + load_classes_in_loader_impl(class_category, class_loader_oop, current); + if (current->has_pending_exception()) { + // We cannot continue, as we might have loaded some of the aot-linked classes, which + // may have dangling C++ pointers to other aot-linked classes that we have failed to load. + exit_on_exception(current); + } +} + +void AOTLinkedClassBulkLoader::exit_on_exception(JavaThread* current) { + assert(current->has_pending_exception(), "precondition"); + ResourceMark rm(current); + if (current->pending_exception()->is_a(vmClasses::OutOfMemoryError_klass())) { + log_error(cds)("Out of memory. Please run with a larger Java heap, current MaxHeapSize = " + SIZE_FORMAT "M", MaxHeapSize/M); + } else { + log_error(cds)("%s: %s", current->pending_exception()->klass()->external_name(), + java_lang_String::as_utf8_string(java_lang_Throwable::message(current->pending_exception()))); + } + vm_exit_during_initialization("Unexpected exception when loading aot-linked classes."); +} + +void AOTLinkedClassBulkLoader::load_classes_in_loader_impl(AOTLinkedClassCategory class_category, oop class_loader_oop, TRAPS) { + Handle h_loader(THREAD, class_loader_oop); + load_table(AOTLinkedClassTable::for_static_archive(), class_category, h_loader, CHECK); + load_table(AOTLinkedClassTable::for_dynamic_archive(), class_category, h_loader, CHECK); + + // Initialize the InstanceKlasses of all archived heap objects that are reachable from the + // archived java class mirrors. + // + // Only the classes in the static archive can have archived mirrors. + AOTLinkedClassTable* static_table = AOTLinkedClassTable::for_static_archive(); + switch (class_category) { + case AOTLinkedClassCategory::BOOT1: + // Delayed until finish_loading_javabase_classes(), as the VM is not ready to + // execute some of the methods. + break; + case AOTLinkedClassCategory::BOOT2: + init_required_classes_for_loader(h_loader, static_table->boot2(), CHECK); + break; + case AOTLinkedClassCategory::PLATFORM: + init_required_classes_for_loader(h_loader, static_table->platform(), CHECK); + break; + case AOTLinkedClassCategory::APP: + init_required_classes_for_loader(h_loader, static_table->app(), CHECK); + break; + case AOTLinkedClassCategory::UNREGISTERED: + ShouldNotReachHere(); + break; + } + + if (Universe::is_fully_initialized() && VerifyDuringStartup) { + // Make sure we're still in a clean state. + VM_Verify verify_op; + VMThread::execute(&verify_op); + } +} + +void AOTLinkedClassBulkLoader::load_table(AOTLinkedClassTable* table, AOTLinkedClassCategory class_category, Handle loader, TRAPS) { + if (class_category != AOTLinkedClassCategory::BOOT1) { + assert(Universe::is_module_initialized(), "sanity"); + } + + const char* category_name = AOTClassLinker::class_category_name(class_category); + switch (class_category) { + case AOTLinkedClassCategory::BOOT1: + load_classes_impl(class_category, table->boot(), category_name, loader, CHECK); + break; + + case AOTLinkedClassCategory::BOOT2: + load_classes_impl(class_category, table->boot2(), category_name, loader, CHECK); + break; + + case AOTLinkedClassCategory::PLATFORM: + { + initiate_loading(THREAD, category_name, loader, table->boot()); + initiate_loading(THREAD, category_name, loader, table->boot2()); + load_classes_impl(class_category, table->platform(), category_name, loader, CHECK); + } + break; + case AOTLinkedClassCategory::APP: + { + initiate_loading(THREAD, category_name, loader, table->boot()); + initiate_loading(THREAD, category_name, loader, table->boot2()); + initiate_loading(THREAD, category_name, loader, table->platform()); + load_classes_impl(class_category, table->app(), category_name, loader, CHECK); + } + break; + case AOTLinkedClassCategory::UNREGISTERED: + default: + ShouldNotReachHere(); // Currently aot-linked classes are not supported for this category. + break; + } +} + +void AOTLinkedClassBulkLoader::load_classes_impl(AOTLinkedClassCategory class_category, Array* classes, + const char* category_name, Handle loader, TRAPS) { + if (classes == nullptr) { + return; + } + + ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(loader()); + + for (int i = 0; i < classes->length(); i++) { + InstanceKlass* ik = classes->at(i); + if (log_is_enabled(Info, cds, aot, load)) { + ResourceMark rm(THREAD); + log_info(cds, aot, load)("%-5s %s%s%s", category_name, ik->external_name(), + ik->is_loaded() ? " (already loaded)" : "", + ik->is_hidden() ? " (hidden)" : ""); + } + + if (!ik->is_loaded()) { + if (ik->is_hidden()) { + load_hidden_class(loader_data, ik, CHECK); + } else { + InstanceKlass* actual; + if (loader_data == ClassLoaderData::the_null_class_loader_data()) { + actual = SystemDictionary::load_instance_class(ik->name(), loader, CHECK); + } else { + actual = SystemDictionaryShared::find_or_load_shared_class(ik->name(), loader, CHECK); + } + + if (actual != ik) { + ResourceMark rm(THREAD); + log_error(cds)("Unable to resolve %s class from CDS archive: %s", category_name, ik->external_name()); + log_error(cds)("Expected: " INTPTR_FORMAT ", actual: " INTPTR_FORMAT, p2i(ik), p2i(actual)); + log_error(cds)("JVMTI class retransformation is not supported when archive was generated with -XX:+AOTClassLinking."); + MetaspaceShared::unrecoverable_loading_error(); + } + assert(actual->is_loaded(), "must be"); + } + } + } +} + +// Initiate loading of the in the . The should have already been loaded +// by a parent loader of the . This is necessary for handling pre-resolved CP entries. +// +// For example, we initiate the loading of java/lang/String in the AppClassLoader. This will allow +// any App classes to have a pre-resolved ConstantPool entry that references java/lang/String. +// +// TODO: we can limit the number of initiated classes to only those that are actually referenced by +// AOT-linked classes loaded by . +void AOTLinkedClassBulkLoader::initiate_loading(JavaThread* current, const char* category_name, + Handle initiating_loader, Array* classes) { + if (classes == nullptr) { + return; + } + + assert(initiating_loader() == SystemDictionary::java_platform_loader() || + initiating_loader() == SystemDictionary::java_system_loader(), "must be"); + ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(initiating_loader()); + MonitorLocker mu1(SystemDictionary_lock); + + for (int i = 0; i < classes->length(); i++) { + InstanceKlass* ik = classes->at(i); + assert(ik->is_loaded(), "must have already been loaded by a parent loader"); + assert(ik->class_loader() != initiating_loader(), "must be a parent loader"); + assert(ik->class_loader() == nullptr || + ik->class_loader() == SystemDictionary::java_platform_loader(), "must be"); + if (ik->is_public() && !ik->is_hidden()) { + if (log_is_enabled(Info, cds, aot, load)) { + ResourceMark rm(current); + const char* defining_loader = (ik->class_loader() == nullptr ? "boot" : "plat"); + log_info(cds, aot, load)("%s %s (initiated, defined by %s)", category_name, ik->external_name(), + defining_loader); + } + SystemDictionary::add_to_initiating_loader(current, ik, loader_data); + } + } +} + +// Currently, we archive only three types of hidden classes: +// - LambdaForms +// - lambda proxy classes +// - StringConcat classes +// See HeapShared::is_archivable_hidden_klass(). +// +// LambdaForm classes (with names like java/lang/invoke/LambdaForm$MH+0x800000015) logically +// belong to the boot loader, but they are usually stored in their own special ClassLoaderData to +// facilitate class unloading, as a LambdaForm may refer to a class loaded by a custom loader +// that may be unloaded. +// +// We only support AOT-resolution of indys in the boot/platform/app loader, so there's no need +// to support class unloading. For simplicity, we put all archived LambdaForm classes in the +// "main" ClassLoaderData of the boot loader. +// +// (Even if we were to support other loaders, we would still feel free to ignore any requirement +// of class unloading, for any class asset in the AOT cache. Anything that makes it into the AOT +// cache has a lifetime dispensation from unloading. After all, the AOT cache never grows, and +// we can assume that the user is content with its size, and doesn't need its footprint to shrink.) +// +// Lambda proxy classes are normally stored in the same ClassLoaderData as their nest hosts, and +// StringConcat are normally stored in the main ClassLoaderData of the boot class loader. We +// do the same for the archived copies of such classes. +void AOTLinkedClassBulkLoader::load_hidden_class(ClassLoaderData* loader_data, InstanceKlass* ik, TRAPS) { + assert(HeapShared::is_lambda_form_klass(ik) || + HeapShared::is_lambda_proxy_klass(ik) || + HeapShared::is_string_concat_klass(ik), "sanity"); + DEBUG_ONLY({ + assert(ik->java_super()->is_loaded(), "must be"); + for (int i = 0; i < ik->local_interfaces()->length(); i++) { + assert(ik->local_interfaces()->at(i)->is_loaded(), "must be"); + } + }); + + Handle pd; + PackageEntry* pkg_entry = nullptr; + + // Since a hidden class does not have a name, it cannot be reloaded + // normally via the system dictionary. Instead, we have to finish the + // loading job here. + + if (HeapShared::is_lambda_proxy_klass(ik)) { + InstanceKlass* nest_host = ik->nest_host_not_null(); + assert(nest_host->is_loaded(), "must be"); + pd = Handle(THREAD, nest_host->protection_domain()); + pkg_entry = nest_host->package(); + } + + ik->restore_unshareable_info(loader_data, pd, pkg_entry, CHECK); + SystemDictionary::load_shared_class_misc(ik, loader_data); + ik->add_to_hierarchy(THREAD); + assert(ik->is_loaded(), "Must be in at least loaded state"); + + DEBUG_ONLY({ + // Make sure we don't make this hidden class available by name, even if we don't + // use any special ClassLoaderData. + Handle loader(THREAD, loader_data->class_loader()); + ResourceMark rm(THREAD); + assert(SystemDictionary::resolve_or_null(ik->name(), loader, pd, THREAD) == nullptr, + "hidden classes cannot be accessible by name: %s", ik->external_name()); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } + }); +} + +void AOTLinkedClassBulkLoader::finish_loading_javabase_classes(TRAPS) { + init_required_classes_for_loader(Handle(), AOTLinkedClassTable::for_static_archive()->boot(), CHECK); +} + +// Some AOT-linked classes for must be initialized early. This includes +// - classes that were AOT-initialized by AOTClassInitializer +// - the classes of all objects that are reachable from the archived mirrors of +// the AOT-linked classes for . +void AOTLinkedClassBulkLoader::init_required_classes_for_loader(Handle class_loader, Array* classes, TRAPS) { + if (classes != nullptr) { + for (int i = 0; i < classes->length(); i++) { + InstanceKlass* ik = classes->at(i); + if (ik->class_loader_data() == nullptr) { + // This class is not yet loaded. We will initialize it in a later phase. + // For example, we have loaded only AOTLinkedClassCategory::BOOT1 classes + // but k is part of AOTLinkedClassCategory::BOOT2. + continue; + } + if (ik->has_aot_initialized_mirror()) { + ik->initialize_with_aot_initialized_mirror(CHECK); + } else { + // Some cached heap objects may hold references to methods in aot-linked + // classes (via MemberName). We need to make sure all classes are + // linked to allow such MemberNames to be invoked. + ik->link_class(CHECK); + } + } + } + + HeapShared::init_classes_for_special_subgraph(class_loader, CHECK); +} + +bool AOTLinkedClassBulkLoader::is_pending_aot_linked_class(Klass* k) { + if (!CDSConfig::is_using_aot_linked_classes()) { + return false; + } + + if (_all_completed) { // no more pending aot-linked classes + return false; + } + + if (k->is_objArray_klass()) { + k = ObjArrayKlass::cast(k)->bottom_klass(); + } + if (!k->is_instance_klass()) { + // type array klasses (and their higher dimensions), + // must have been loaded before a GC can ever happen. + return false; + } + + // There's a small window during VM start-up where a not-yet loaded aot-linked + // class k may be discovered by the GC during VM initialization. This can happen + // when the heap contains an aot-cached instance of k, but k is not ready to be + // loaded yet. (TODO: JDK-8342429 eliminates this possibility) + // + // The following checks try to limit this window as much as possible for each of + // the four AOTLinkedClassCategory of classes that can be aot-linked. + + InstanceKlass* ik = InstanceKlass::cast(k); + if (ik->is_shared_boot_class()) { + if (ik->module() != nullptr && ik->in_javabase_module()) { + // AOTLinkedClassCategory::BOOT1 -- all aot-linked classes in + // java.base must have been loaded before a GC can ever happen. + return false; + } else { + // AOTLinkedClassCategory::BOOT2 classes cannot be loaded until + // module system is ready. + return !_boot2_completed; + } + } else if (ik->is_shared_platform_class()) { + // AOTLinkedClassCategory::PLATFORM classes cannot be loaded until + // the platform class loader is initialized. + return !_platform_completed; + } else if (ik->is_shared_app_class()) { + // AOTLinkedClassCategory::APP cannot be loaded until the app class loader + // is initialized. + return !_app_completed; + } else { + return false; + } +} diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp new file mode 100644 index 00000000000..a8e6365b899 --- /dev/null +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +#ifndef SHARE_CDS_AOTLINKEDCLASSBULKLOADER_HPP +#define SHARE_CDS_AOTLINKEDCLASSBULKLOADER_HPP + +#include "memory/allStatic.hpp" +#include "memory/allocation.hpp" +#include "runtime/handles.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/macros.hpp" + +class AOTLinkedClassTable; +class ClassLoaderData; +class InstanceKlass; +class SerializeClosure; +template class Array; +enum class AOTLinkedClassCategory : int; + +// During a Production Run, the AOTLinkedClassBulkLoader loads all classes from +// a AOTLinkedClassTable into their respective ClassLoaders. This happens very early +// in the JVM bootstrap stage, before any application code is executed. +// +class AOTLinkedClassBulkLoader : AllStatic { + static bool _boot2_completed; + static bool _platform_completed; + static bool _app_completed; + static bool _all_completed; + static void load_classes_in_loader(JavaThread* current, AOTLinkedClassCategory class_category, oop class_loader_oop); + static void load_classes_in_loader_impl(AOTLinkedClassCategory class_category, oop class_loader_oop, TRAPS); + static void load_table(AOTLinkedClassTable* table, AOTLinkedClassCategory class_category, Handle loader, TRAPS); + static void initiate_loading(JavaThread* current, const char* category, Handle initiating_loader, Array* classes); + static void load_classes_impl(AOTLinkedClassCategory class_category, Array* classes, + const char* category_name, Handle loader, TRAPS); + static void load_hidden_class(ClassLoaderData* loader_data, InstanceKlass* ik, TRAPS); + static void init_required_classes_for_loader(Handle class_loader, Array* classes, TRAPS); +public: + static void serialize(SerializeClosure* soc, bool is_static_archive) NOT_CDS_RETURN; + + static void load_javabase_classes(JavaThread* current) NOT_CDS_RETURN; + static void load_non_javabase_classes(JavaThread* current) NOT_CDS_RETURN; + static void finish_loading_javabase_classes(TRAPS) NOT_CDS_RETURN; + static void exit_on_exception(JavaThread* current); + + static bool is_pending_aot_linked_class(Klass* k) NOT_CDS_RETURN_(false); +}; + +#endif // SHARE_CDS_AOTLINKEDCLASSBULKLOADER_HPP diff --git a/src/hotspot/share/cds/aotLinkedClassTable.cpp b/src/hotspot/share/cds/aotLinkedClassTable.cpp new file mode 100644 index 00000000000..bed090f00a9 --- /dev/null +++ b/src/hotspot/share/cds/aotLinkedClassTable.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotLinkedClassTable.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/serializeClosure.hpp" +#include "oops/array.hpp" + +AOTLinkedClassTable AOTLinkedClassTable::_for_static_archive; +AOTLinkedClassTable AOTLinkedClassTable::_for_dynamic_archive; + +void AOTLinkedClassTable::serialize(SerializeClosure* soc) { + soc->do_ptr((void**)&_boot); + soc->do_ptr((void**)&_boot2); + soc->do_ptr((void**)&_platform); + soc->do_ptr((void**)&_app); +} + diff --git a/src/hotspot/share/cds/aotLinkedClassTable.hpp b/src/hotspot/share/cds/aotLinkedClassTable.hpp new file mode 100644 index 00000000000..2a199c15edd --- /dev/null +++ b/src/hotspot/share/cds/aotLinkedClassTable.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +#ifndef SHARE_CDS_AOTLINKEDCLASSTABLE_HPP +#define SHARE_CDS_AOTLINKEDCLASSTABLE_HPP + +#include "utilities/globalDefinitions.hpp" + +template class Array; +class InstanceKlass; +class SerializeClosure; + +// Classes to be bulk-loaded, in the "linked" state, at VM bootstrap. +// +// AOTLinkedClassTable is produced by AOTClassLinker when an AOTCache is assembled. +// +// AOTLinkedClassTable is consumed by AOTLinkedClassBulkLoader when an AOTCache is used +// in a production run. +// +class AOTLinkedClassTable { + // The VM may load up to 2 CDS archives -- static and dynamic. Each + // archive can have its own AOTLinkedClassTable. + static AOTLinkedClassTable _for_static_archive; + static AOTLinkedClassTable _for_dynamic_archive; + + Array* _boot; // only java.base classes + Array* _boot2; // boot classes in other modules + Array* _platform; + Array* _app; + +public: + AOTLinkedClassTable() : + _boot(nullptr), _boot2(nullptr), + _platform(nullptr), _app(nullptr) {} + + static AOTLinkedClassTable* for_static_archive() { return &_for_static_archive; } + static AOTLinkedClassTable* for_dynamic_archive() { return &_for_dynamic_archive; } + + static AOTLinkedClassTable* get(bool is_static_archive) { + return is_static_archive ? for_static_archive() : for_dynamic_archive(); + } + + Array* boot() const { return _boot; } + Array* boot2() const { return _boot2; } + Array* platform() const { return _platform; } + Array* app() const { return _app; } + + void set_boot (Array* value) { _boot = value; } + void set_boot2 (Array* value) { _boot2 = value; } + void set_platform(Array* value) { _platform = value; } + void set_app (Array* value) { _app = value; } + + void serialize(SerializeClosure* soc); +}; + +#endif // SHARE_CDS_AOTLINKEDCLASSTABLE_HPP diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index a181257a519..da4319955ea 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -23,6 +23,8 @@ */ #include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapWriter.hpp" #include "cds/archiveUtils.hpp" @@ -33,7 +35,9 @@ #include "cds/heapShared.hpp" #include "cds/metaspaceShared.hpp" #include "cds/regeneratedClasses.hpp" +#include "classfile/classLoader.hpp" #include "classfile/classLoaderDataShared.hpp" +#include "classfile/classLoaderExt.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -226,6 +230,10 @@ bool ArchiveBuilder::gather_klass_and_symbol(MetaspaceClosure::Ref* ref, bool re assert(klass->is_klass(), "must be"); if (!is_excluded(klass)) { _klasses->append(klass); + if (klass->is_hidden()) { + assert(klass->is_instance_klass(), "must be"); + assert(SystemDictionaryShared::should_hidden_class_be_archived(InstanceKlass::cast(klass)), "must be"); + } } // See RunTimeClassInfo::get_for(): make sure we have enough space for both maximum // Klass alignment as well as the RuntimeInfo* pointer we will embed in front of a Klass. @@ -284,6 +292,8 @@ void ArchiveBuilder::gather_klasses_and_symbols() { // but this should be enough for now _estimated_metaspaceobj_bytes += 200 * 1024 * 1024; } + + AOTClassLinker::add_candidates(); } int ArchiveBuilder::compare_symbols_by_address(Symbol** a, Symbol** b) { @@ -310,6 +320,15 @@ size_t ArchiveBuilder::estimate_archive_size() { size_t dictionary_est = SystemDictionaryShared::estimate_size_for_archive(); _estimated_hashtable_bytes = symbol_table_est + dictionary_est; + if (CDSConfig::is_dumping_aot_linked_classes()) { + // This is difficult to estimate when dumping the dynamic archive, as the + // AOTLinkedClassTable may need to contain classes in the static archive as well. + // + // Just give a generous estimate for now. We will remove estimate_archive_size() + // in JDK-8340416 + _estimated_hashtable_bytes += 20 * 1024 * 1024; + } + size_t total = 0; total += _estimated_metaspaceobj_bytes; @@ -423,11 +442,12 @@ bool ArchiveBuilder::gather_one_source_obj(MetaspaceClosure::Ref* ref, bool read if (src_obj == nullptr) { return false; } + + remember_embedded_pointer_in_enclosing_obj(ref); if (RegeneratedClasses::has_been_regenerated(src_obj)) { // No need to copy it. We will later relocate it to point to the regenerated klass/method. return false; } - remember_embedded_pointer_in_enclosing_obj(ref); FollowMode follow_mode = get_follow_mode(ref); SourceObjInfo src_info(ref, read_only, follow_mode); @@ -740,6 +760,16 @@ void ArchiveBuilder::mark_and_relocate_to_buffered_addr(address* ptr_location) { ArchivePtrMarker::mark_pointer(ptr_location); } +bool ArchiveBuilder::has_been_buffered(address src_addr) const { + if (RegeneratedClasses::has_been_regenerated(src_addr) || + _src_obj_table.get(src_addr) == nullptr || + get_buffered_addr(src_addr) == nullptr) { + return false; + } else { + return true; + } +} + address ArchiveBuilder::get_buffered_addr(address src_addr) const { SourceObjInfo* p = _src_obj_table.get(src_addr); assert(p != nullptr, "src_addr " INTPTR_FORMAT " is used but has not been archived", @@ -767,17 +797,35 @@ void ArchiveBuilder::relocate_metaspaceobj_embedded_pointers() { relocate_embedded_pointers(&_ro_src_objs); } +#define ADD_COUNT(x) \ + x += 1; \ + x ## _a += aotlinked ? 1 : 0; \ + x ## _i += inited ? 1 : 0; + +#define DECLARE_INSTANCE_KLASS_COUNTER(x) \ + int x = 0; \ + int x ## _a = 0; \ + int x ## _i = 0; + void ArchiveBuilder::make_klasses_shareable() { - int num_instance_klasses = 0; - int num_boot_klasses = 0; - int num_platform_klasses = 0; - int num_app_klasses = 0; - int num_hidden_klasses = 0; + DECLARE_INSTANCE_KLASS_COUNTER(num_instance_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_boot_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_vm_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_platform_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_app_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_old_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_hidden_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_enum_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_unregistered_klasses); int num_unlinked_klasses = 0; - int num_unregistered_klasses = 0; int num_obj_array_klasses = 0; int num_type_array_klasses = 0; + int boot_unlinked = 0; + int platform_unlinked = 0; + int app_unlinked = 0; + int unreg_unlinked = 0; + for (int i = 0; i < klasses()->length(); i++) { // Some of the code in ConstantPool::remove_unshareable_info() requires the classes // to be in linked state, so it must be call here before the next loop, which returns @@ -791,8 +839,12 @@ void ArchiveBuilder::make_klasses_shareable() { for (int i = 0; i < klasses()->length(); i++) { const char* type; const char* unlinked = ""; + const char* kind = ""; const char* hidden = ""; + const char* old = ""; const char* generated = ""; + const char* aotlinked_msg = ""; + const char* inited_msg = ""; Klass* k = get_buffered_addr(klasses()->at(i)); k->remove_java_mirror(); #ifdef _LP64 @@ -815,60 +867,127 @@ void ArchiveBuilder::make_klasses_shareable() { k->remove_unshareable_info(); } else { assert(k->is_instance_klass(), " must be"); - num_instance_klasses ++; InstanceKlass* ik = InstanceKlass::cast(k); - if (ik->is_shared_boot_class()) { + InstanceKlass* src_ik = get_source_addr(ik); + bool aotlinked = AOTClassLinker::is_candidate(src_ik); + bool inited = ik->has_aot_initialized_mirror(); + ADD_COUNT(num_instance_klasses); + if (CDSConfig::is_dumping_dynamic_archive()) { + // For static dump, class loader type are already set. + ik->assign_class_loader_type(); + } + if (ik->is_hidden()) { + ADD_COUNT(num_hidden_klasses); + hidden = " hidden"; + oop loader = k->class_loader(); + if (loader == nullptr) { + type = "boot"; + ADD_COUNT(num_boot_klasses); + } else if (loader == SystemDictionary::java_platform_loader()) { + type = "plat"; + ADD_COUNT(num_platform_klasses); + } else if (loader == SystemDictionary::java_system_loader()) { + type = "app"; + ADD_COUNT(num_app_klasses); + } else { + type = "bad"; + assert(0, "shouldn't happen"); + } + if (CDSConfig::is_dumping_invokedynamic()) { + assert(HeapShared::is_archivable_hidden_klass(ik), "sanity"); + } else { + // Legacy CDS support for lambda proxies + assert(HeapShared::is_lambda_proxy_klass(ik), "sanity"); + } + } else if (ik->is_shared_boot_class()) { type = "boot"; - num_boot_klasses ++; + ADD_COUNT(num_boot_klasses); } else if (ik->is_shared_platform_class()) { type = "plat"; - num_platform_klasses ++; + ADD_COUNT(num_platform_klasses); } else if (ik->is_shared_app_class()) { type = "app"; - num_app_klasses ++; + ADD_COUNT(num_app_klasses); } else { assert(ik->is_shared_unregistered_class(), "must be"); type = "unreg"; - num_unregistered_klasses ++; + ADD_COUNT(num_unregistered_klasses); + } + + if (AOTClassLinker::is_vm_class(src_ik)) { + ADD_COUNT(num_vm_klasses); } if (!ik->is_linked()) { num_unlinked_klasses ++; - unlinked = " ** unlinked"; + unlinked = " unlinked"; + if (ik->is_shared_boot_class()) { + boot_unlinked ++; + } else if (ik->is_shared_platform_class()) { + platform_unlinked ++; + } else if (ik->is_shared_app_class()) { + app_unlinked ++; + } else { + unreg_unlinked ++; + } } - if (ik->is_hidden()) { - num_hidden_klasses ++; - hidden = " ** hidden"; + if (ik->is_interface()) { + kind = " interface"; + } else if (src_ik->is_enum_subclass()) { + kind = " enum"; + ADD_COUNT(num_enum_klasses); + } + + if (!ik->can_be_verified_at_dumptime()) { + ADD_COUNT(num_old_klasses); + old = " old"; } if (ik->is_generated_shared_class()) { - generated = " ** generated"; + generated = " generated"; } + if (aotlinked) { + aotlinked_msg = " aot-linked"; + } + if (inited) { + inited_msg = " inited"; + } + MetaspaceShared::rewrite_nofast_bytecodes_and_calculate_fingerprints(Thread::current(), ik); ik->remove_unshareable_info(); } if (log_is_enabled(Debug, cds, class)) { ResourceMark rm; - log_debug(cds, class)("klasses[%5d] = " PTR_FORMAT " %-5s %s%s%s%s", i, + log_debug(cds, class)("klasses[%5d] = " PTR_FORMAT " %-5s %s%s%s%s%s%s%s%s", i, p2i(to_requested(k)), type, k->external_name(), - hidden, unlinked, generated); + kind, hidden, old, unlinked, generated, aotlinked_msg, inited_msg); } } +#define STATS_FORMAT "= %5d, aot-linked = %5d, inited = %5d" +#define STATS_PARAMS(x) num_ ## x, num_ ## x ## _a, num_ ## x ## _i + log_info(cds)("Number of classes %d", num_instance_klasses + num_obj_array_klasses + num_type_array_klasses); - log_info(cds)(" instance classes = %5d", num_instance_klasses); - log_info(cds)(" boot = %5d", num_boot_klasses); - log_info(cds)(" app = %5d", num_app_klasses); - log_info(cds)(" platform = %5d", num_platform_klasses); - log_info(cds)(" unregistered = %5d", num_unregistered_klasses); - log_info(cds)(" (hidden) = %5d", num_hidden_klasses); - log_info(cds)(" (unlinked) = %5d", num_unlinked_klasses); + log_info(cds)(" instance classes " STATS_FORMAT, STATS_PARAMS(instance_klasses)); + log_info(cds)(" boot " STATS_FORMAT, STATS_PARAMS(boot_klasses)); + log_info(cds)(" vm " STATS_FORMAT, STATS_PARAMS(vm_klasses)); + log_info(cds)(" platform " STATS_FORMAT, STATS_PARAMS(platform_klasses)); + log_info(cds)(" app " STATS_FORMAT, STATS_PARAMS(app_klasses)); + log_info(cds)(" unregistered " STATS_FORMAT, STATS_PARAMS(unregistered_klasses)); + log_info(cds)(" (enum) " STATS_FORMAT, STATS_PARAMS(enum_klasses)); + log_info(cds)(" (hidden) " STATS_FORMAT, STATS_PARAMS(hidden_klasses)); + log_info(cds)(" (old) " STATS_FORMAT, STATS_PARAMS(old_klasses)); + log_info(cds)(" (unlinked) = %5d, boot = %d, plat = %d, app = %d, unreg = %d", + num_unlinked_klasses, boot_unlinked, platform_unlinked, app_unlinked, unreg_unlinked); log_info(cds)(" obj array classes = %5d", num_obj_array_klasses); log_info(cds)(" type array classes = %5d", num_type_array_klasses); log_info(cds)(" symbols = %5d", _symbols->length()); +#undef STATS_FORMAT +#undef STATS_PARAMS + DynamicArchive::make_array_klasses_shareable(); } @@ -876,6 +995,7 @@ void ArchiveBuilder::serialize_dynamic_archivable_items(SerializeClosure* soc) { SymbolTable::serialize_shared_table_header(soc, false); SystemDictionaryShared::serialize_dictionary_headers(soc, false); DynamicArchive::serialize_array_klasses(soc); + AOTLinkedClassBulkLoader::serialize(soc, false); } uintx ArchiveBuilder::buffer_to_offset(address p) const { diff --git a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp index f306e4676b3..39fffc26c37 100644 --- a/src/hotspot/share/cds/archiveBuilder.hpp +++ b/src/hotspot/share/cds/archiveBuilder.hpp @@ -292,7 +292,7 @@ public: intx buffer_to_requested_delta() const { return _buffer_to_requested_delta; } bool is_in_buffer_space(address p) const { - return (buffer_bottom() <= p && p < buffer_top()); + return (buffer_bottom() != nullptr && buffer_bottom() <= p && p < buffer_top()); } template bool is_in_requested_static_archive(T p) const { @@ -420,6 +420,11 @@ public: mark_and_relocate_to_buffered_addr((address*)ptr_location); } + bool has_been_buffered(address src_addr) const; + template bool has_been_buffered(T src_addr) const { + return has_been_buffered((address)src_addr); + } + address get_buffered_addr(address src_addr) const; template T get_buffered_addr(T src_addr) const { return (T)get_buffered_addr((address)src_addr); diff --git a/src/hotspot/share/cds/archiveHeapLoader.cpp b/src/hotspot/share/cds/archiveHeapLoader.cpp index 01831cf0f3e..b05fd20f4f5 100644 --- a/src/hotspot/share/cds/archiveHeapLoader.cpp +++ b/src/hotspot/share/cds/archiveHeapLoader.cpp @@ -449,10 +449,6 @@ class PatchNativePointers: public BitMapClosure { bool do_bit(size_t offset) { Metadata** p = _start + offset; *p = (Metadata*)(address(*p) + MetaspaceShared::relocation_delta()); - // Currently we have only Klass pointers in heap objects. - // This needs to be relaxed when we support other types of native - // pointers such as Method. - assert(((Klass*)(*p))->is_klass(), "must be"); return true; } }; diff --git a/src/hotspot/share/cds/archiveHeapWriter.cpp b/src/hotspot/share/cds/archiveHeapWriter.cpp index 636b368117d..be821044a96 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.cpp +++ b/src/hotspot/share/cds/archiveHeapWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -27,14 +27,15 @@ #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" #include "cds/heapShared.hpp" +#include "classfile/javaClasses.hpp" #include "classfile/systemDictionary.hpp" #include "gc/shared/collectedHeap.hpp" #include "memory/iterator.inline.hpp" #include "memory/oopFactory.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.hpp" -#include "oops/oop.inline.hpp" #include "oops/objArrayOop.inline.hpp" +#include "oops/oop.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "oops/typeArrayKlass.hpp" #include "oops/typeArrayOop.hpp" @@ -535,7 +536,14 @@ oop ArchiveHeapWriter::load_oop_from_buffer(narrowOop* buffered_addr) { template void ArchiveHeapWriter::relocate_field_in_buffer(T* field_addr_in_buffer, CHeapBitMap* oopmap) { oop source_referent = load_source_oop_from_buffer(field_addr_in_buffer); - if (!CompressedOops::is_null(source_referent)) { + if (source_referent != nullptr) { + if (java_lang_Class::is_instance(source_referent)) { + // When the source object points to a "real" mirror, the buffered object should point + // to the "scratch" mirror, which has all unarchivable fields scrubbed (to be reinstated + // at run time). + source_referent = HeapShared::scratch_java_mirror(source_referent); + assert(source_referent != nullptr, "must be"); + } oop request_referent = source_obj_to_requested_obj(source_referent); store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); mark_oop_pointer(field_addr_in_buffer, oopmap); @@ -731,7 +739,9 @@ void ArchiveHeapWriter::compute_ptrmap(ArchiveHeapInfo* heap_info) { Metadata** buffered_field_addr = requested_addr_to_buffered_addr(requested_field_addr); Metadata* native_ptr = *buffered_field_addr; - assert(native_ptr != nullptr, "sanity"); + guarantee(native_ptr != nullptr, "sanity"); + guarantee(ArchiveBuilder::current()->has_been_buffered((address)native_ptr), + "Metadata %p should have been archived", native_ptr); address buffered_native_ptr = ArchiveBuilder::current()->get_buffered_addr((address)native_ptr); address requested_native_ptr = ArchiveBuilder::current()->to_requested(buffered_native_ptr); diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index c7282f7d97c..4d717879e0f 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -39,6 +39,7 @@ #include "memory/metaspaceUtils.hpp" #include "memory/resourceArea.hpp" #include "oops/compressedOops.inline.hpp" +#include "oops/klass.inline.hpp" #include "runtime/arguments.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/debug.hpp" @@ -370,6 +371,14 @@ void ArchiveUtils::log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) { } } +bool ArchiveUtils::has_aot_initialized_mirror(InstanceKlass* src_ik) { + if (SystemDictionaryShared::is_excluded_class(src_ik)) { + assert(!ArchiveBuilder::current()->has_been_buffered(src_ik), "sanity"); + return false; + } + return ArchiveBuilder::current()->get_buffered_addr(src_ik)->has_aot_initialized_mirror(); +} + size_t HeapRootSegments::size_in_bytes(size_t seg_idx) { assert(seg_idx < _count, "In range"); return objArrayOopDesc::object_size(size_in_elems(seg_idx)) * HeapWordSize; diff --git a/src/hotspot/share/cds/archiveUtils.hpp b/src/hotspot/share/cds/archiveUtils.hpp index 7fb8e538084..606f5137e9d 100644 --- a/src/hotspot/share/cds/archiveUtils.hpp +++ b/src/hotspot/share/cds/archiveUtils.hpp @@ -38,6 +38,9 @@ class BootstrapInfo; class ReservedSpace; class VirtualSpace; +template class Array; +template class GrowableArray; + // ArchivePtrMarker is used to mark the location of pointers embedded in a CDS archive. E.g., when an // InstanceKlass k is dumped, we mark the location of the k->_name pointer by effectively calling // mark_pointer(/*ptr_loc=*/&k->_name). It's required that (_prt_base <= ptr_loc < _ptr_end). _ptr_base is @@ -253,6 +256,8 @@ class ArchiveUtils { public: static const uintx MAX_SHARED_DELTA = 0x7FFFFFFF; static void log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) NOT_CDS_RETURN; + static bool has_aot_initialized_mirror(InstanceKlass* src_ik); + template static Array* archive_array(GrowableArray* tmp_array); // offset must represent an object of type T in the mapped shared space. Return // a direct pointer to this object. diff --git a/src/hotspot/share/cds/archiveUtils.inline.hpp b/src/hotspot/share/cds/archiveUtils.inline.hpp index 7df344e3701..537b3d1670c 100644 --- a/src/hotspot/share/cds/archiveUtils.inline.hpp +++ b/src/hotspot/share/cds/archiveUtils.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -27,7 +27,10 @@ #include "cds/archiveUtils.hpp" +#include "cds/archiveBuilder.hpp" +#include "oops/array.hpp" #include "utilities/bitMap.inline.hpp" +#include "utilities/growableArray.hpp" inline bool SharedDataRelocator::do_bit(size_t offset) { address* p = _patch_base + offset; @@ -47,4 +50,19 @@ inline bool SharedDataRelocator::do_bit(size_t offset) { return true; // keep iterating } +// Returns the address of an Array that's allocated in the ArchiveBuilder "buffer" space. +template +Array* ArchiveUtils::archive_array(GrowableArray* tmp_array) { + Array* archived_array = ArchiveBuilder::new_ro_array(tmp_array->length()); + for (int i = 0; i < tmp_array->length(); i++) { + archived_array->at_put(i, tmp_array->at(i)); + if (std::is_pointer::value) { + ArchivePtrMarker::mark_pointer(archived_array->adr_at(i)); + } + } + + return archived_array; +} + + #endif // SHARE_CDS_ARCHIVEUTILS_INLINE_HPP diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 691a1983732..ba5d12e6450 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -33,19 +33,27 @@ #include "logging/log.hpp" #include "memory/universe.hpp" #include "runtime/arguments.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/java.hpp" +#include "runtime/vmThread.hpp" #include "utilities/defaultStream.hpp" +#include "utilities/formatBuffer.hpp" bool CDSConfig::_is_dumping_static_archive = false; bool CDSConfig::_is_dumping_dynamic_archive = false; bool CDSConfig::_is_using_optimized_module_handling = true; bool CDSConfig::_is_dumping_full_module_graph = true; bool CDSConfig::_is_using_full_module_graph = true; +bool CDSConfig::_has_aot_linked_classes = false; +bool CDSConfig::_has_archived_invokedynamic = false; +bool CDSConfig::_old_cds_flags_used = false; char* CDSConfig::_default_archive_path = nullptr; char* CDSConfig::_static_archive_path = nullptr; char* CDSConfig::_dynamic_archive_path = nullptr; +JavaThread* CDSConfig::_dumper_thread = nullptr; + int CDSConfig::get_status() { assert(Universe::is_fully_initialized(), "status is finalized only after Universe is initialized"); return (is_dumping_archive() ? IS_DUMPING_ARCHIVE : 0) | @@ -54,7 +62,6 @@ int CDSConfig::get_status() { (is_using_archive() ? IS_USING_ARCHIVE : 0); } - void CDSConfig::initialize() { if (is_dumping_static_archive()) { if (RequireSharedSpaces) { @@ -334,7 +341,95 @@ bool CDSConfig::has_unsupported_runtime_module_options() { return false; } +#define CHECK_ALIAS(f) check_flag_alias(FLAG_IS_DEFAULT(f), #f) + +void CDSConfig::check_flag_alias(bool alias_is_default, const char* alias_name) { + if (_old_cds_flags_used && !alias_is_default) { + vm_exit_during_initialization(err_msg("Option %s cannot be used at the same time with " + "-Xshare:on, -Xshare:auto, -Xshare:off, -Xshare:dump, " + "DumpLoadedClassList, SharedClassListFile, or SharedArchiveFile", + alias_name)); + } +} + +void CDSConfig::check_flag_aliases() { + if (!FLAG_IS_DEFAULT(DumpLoadedClassList) || + !FLAG_IS_DEFAULT(SharedClassListFile) || + !FLAG_IS_DEFAULT(SharedArchiveFile)) { + _old_cds_flags_used = true; + } + + CHECK_ALIAS(AOTCache); + CHECK_ALIAS(AOTConfiguration); + CHECK_ALIAS(AOTMode); + + if (FLAG_IS_DEFAULT(AOTCache) && FLAG_IS_DEFAULT(AOTConfiguration) && FLAG_IS_DEFAULT(AOTMode)) { + // Aliases not used. + return; + } + + if (FLAG_IS_DEFAULT(AOTMode) || strcmp(AOTMode, "auto") == 0 || strcmp(AOTMode, "on") == 0) { + if (!FLAG_IS_DEFAULT(AOTConfiguration)) { + vm_exit_during_initialization("AOTConfiguration can only be used with -XX:AOTMode=record or -XX:AOTMode=create"); + } + + if (!FLAG_IS_DEFAULT(AOTCache)) { + assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked"); + FLAG_SET_ERGO(SharedArchiveFile, AOTCache); + } + + UseSharedSpaces = true; + if (FLAG_IS_DEFAULT(AOTMode) || (strcmp(AOTMode, "auto") == 0)) { + RequireSharedSpaces = false; + } else { + assert(strcmp(AOTMode, "on") == 0, "already checked"); + RequireSharedSpaces = true; + } + } else if (strcmp(AOTMode, "off") == 0) { + UseSharedSpaces = false; + RequireSharedSpaces = false; + } else { + // AOTMode is record or create + if (FLAG_IS_DEFAULT(AOTConfiguration)) { + vm_exit_during_initialization(err_msg("-XX:AOTMode=%s cannot be used without setting AOTConfiguration", AOTMode)); + } + + if (strcmp(AOTMode, "record") == 0) { + if (!FLAG_IS_DEFAULT(AOTCache)) { + vm_exit_during_initialization("AOTCache must not be specified when using -XX:AOTMode=record"); + } + + assert(FLAG_IS_DEFAULT(DumpLoadedClassList), "already checked"); + FLAG_SET_ERGO(DumpLoadedClassList, AOTConfiguration); + UseSharedSpaces = false; + RequireSharedSpaces = false; + } else { + assert(strcmp(AOTMode, "create") == 0, "checked by AOTModeConstraintFunc"); + if (FLAG_IS_DEFAULT(AOTCache)) { + vm_exit_during_initialization("AOTCache must be specified when using -XX:AOTMode=create"); + } + + assert(FLAG_IS_DEFAULT(SharedClassListFile), "already checked"); + FLAG_SET_ERGO(SharedClassListFile, AOTConfiguration); + assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked"); + FLAG_SET_ERGO(SharedArchiveFile, AOTCache); + + CDSConfig::enable_dumping_static_archive(); + } + } +} + bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_flag_cmd_line) { + check_flag_aliases(); + + if (AOTClassLinking) { + // If AOTClassLinking is specified, enable all AOT optimizations by default. + FLAG_SET_ERGO_IF_DEFAULT(AOTInvokeDynamicLinking, true); + } else { + // AOTInvokeDynamicLinking depends on AOTClassLinking. + FLAG_SET_ERGO(AOTInvokeDynamicLinking, false); + } + if (is_dumping_static_archive()) { if (!mode_flag_cmd_line) { // By default, -Xshare:dump runs in interpreter-only mode, which is required for deterministic archive. @@ -353,6 +448,9 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla // run to another which resulting in non-determinstic CDS archives. // Disable UseStringDeduplication while dumping CDS archive. UseStringDeduplication = false; + + // Don't use SoftReferences so that objects used by java.lang.invoke tables can be archived. + Arguments::PropertyList_add(new SystemProperty("java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE", "false", false)); } // RecordDynamicDumpInfo is not compatible with ArchiveClassesAtExit @@ -397,6 +495,11 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla return true; } +bool CDSConfig::allow_only_single_java_thread() { + // See comments in JVM_StartThread() + return is_dumping_static_archive(); +} + bool CDSConfig::is_using_archive() { return UseSharedSpaces; } @@ -411,12 +514,32 @@ void CDSConfig::stop_using_optimized_module_handling() { _is_using_full_module_graph = false; // This requires is_using_optimized_module_handling() } + +CDSConfig::DumperThreadMark::DumperThreadMark(JavaThread* current) { + assert(_dumper_thread == nullptr, "sanity"); + _dumper_thread = current; +} + +CDSConfig::DumperThreadMark::~DumperThreadMark() { + assert(_dumper_thread != nullptr, "sanity"); + _dumper_thread = nullptr; +} + +bool CDSConfig::current_thread_is_vm_or_dumper() { + Thread* t = Thread::current(); + return t != nullptr && (t->is_VM_thread() || t == _dumper_thread); +} + #if INCLUDE_CDS_JAVA_HEAP bool CDSConfig::is_dumping_heap() { // heap dump is not supported in dynamic dump return is_dumping_static_archive() && HeapShared::can_write(); } +bool CDSConfig::is_loading_heap() { + return ArchiveHeapLoader::is_in_use(); +} + bool CDSConfig::is_using_full_module_graph() { if (ClassLoaderDataShared::is_full_module_graph_loaded()) { return true; @@ -455,4 +578,39 @@ void CDSConfig::stop_using_full_module_graph(const char* reason) { } } } + +bool CDSConfig::is_dumping_aot_linked_classes() { + if (is_dumping_dynamic_archive()) { + return is_using_full_module_graph() && AOTClassLinking; + } else if (is_dumping_static_archive()) { + return is_dumping_full_module_graph() && AOTClassLinking; + } else { + return false; + } +} + +bool CDSConfig::is_using_aot_linked_classes() { + // Make sure we have the exact same module graph as in the assembly phase, or else + // some aot-linked classes may not be visible so cannot be loaded. + return is_using_full_module_graph() && _has_aot_linked_classes; +} + +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(); +} + +bool CDSConfig::is_loading_invokedynamic() { + return UseSharedSpaces && is_using_full_module_graph() && _has_archived_invokedynamic; +} + #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index 7711a2ea1e2..cceaceeb6d7 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -29,6 +29,8 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" +class JavaThread; + class CDSConfig : public AllStatic { #if INCLUDE_CDS static bool _is_dumping_static_archive; @@ -36,10 +38,16 @@ class CDSConfig : public AllStatic { static bool _is_using_optimized_module_handling; static bool _is_dumping_full_module_graph; static bool _is_using_full_module_graph; + static bool _has_aot_linked_classes; + static bool _has_archived_invokedynamic; static char* _default_archive_path; static char* _static_archive_path; static char* _dynamic_archive_path; + + static bool _old_cds_flags_used; + + static JavaThread* _dumper_thread; #endif static void extract_shared_archive_paths(const char* archive_path, @@ -47,6 +55,9 @@ class CDSConfig : public AllStatic { char** top_archive_path); static void init_shared_archive_paths(); + static void check_flag_alias(bool alias_is_default, const char* alias_name); + static void check_flag_aliases(); + public: // Used by jdk.internal.misc.CDS.getCDSConfigStatus(); static const int IS_DUMPING_ARCHIVE = 1 << 0; @@ -57,6 +68,8 @@ public: // Initialization and command-line checking static void initialize() NOT_CDS_RETURN; + static void set_old_cds_flags_used() { CDS_ONLY(_old_cds_flags_used = true); } + static bool old_cds_flags_used() { return CDS_ONLY(_old_cds_flags_used) NOT_CDS(false); } static void check_internal_module_property(const char* key, const char* value) NOT_CDS_RETURN; static void check_incompatible_property(const char* key, const char* value) NOT_CDS_RETURN; static void check_unsupported_dumping_module_options() NOT_CDS_RETURN; @@ -79,12 +92,19 @@ public: static void enable_dumping_dynamic_archive() { CDS_ONLY(_is_dumping_dynamic_archive = true); } static void disable_dumping_dynamic_archive() { CDS_ONLY(_is_dumping_dynamic_archive = false); } + // Misc CDS features + static bool allow_only_single_java_thread() NOT_CDS_RETURN_(false); + // optimized_module_handling -- can we skip some expensive operations related to modules? static bool is_using_optimized_module_handling() { return CDS_ONLY(_is_using_optimized_module_handling) NOT_CDS(false); } static void stop_using_optimized_module_handling() NOT_CDS_RETURN; static bool is_logging_lambda_form_invokers() NOT_CDS_RETURN_(false); + static bool is_dumping_aot_linked_classes() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_using_aot_linked_classes() NOT_CDS_JAVA_HEAP_RETURN_(false); + static void set_has_aot_linked_classes(bool has_aot_linked_classes) NOT_CDS_JAVA_HEAP_RETURN; + // archive_path // Points to the classes.jsa in $JAVA_HOME @@ -96,13 +116,34 @@ public: // --- Archived java objects - static bool is_dumping_heap() NOT_CDS_JAVA_HEAP_RETURN_(false); + 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_loading_invokedynamic() NOT_CDS_JAVA_HEAP_RETURN_(false); + static void set_has_archived_invokedynamic() { CDS_JAVA_HEAP_ONLY(_has_archived_invokedynamic = true); } // full_module_graph (requires optimized_module_handling) static bool is_dumping_full_module_graph() { return CDS_ONLY(_is_dumping_full_module_graph) NOT_CDS(false); } static bool is_using_full_module_graph() NOT_CDS_JAVA_HEAP_RETURN_(false); static void stop_dumping_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN; static void stop_using_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN; + + + // Some CDS functions assume that they are called only within a single-threaded context. I.e., + // they are called from: + // - The VM thread (e.g., inside VM_PopulateDumpSharedSpace) + // - The thread that performs prepatory steps before switching to the VM thread + // Since these two threads never execute concurrently, we can avoid using locks in these CDS + // function. For safety, these functions should assert with CDSConfig::current_thread_is_vm_or_dumper(). + class DumperThreadMark { + public: + DumperThreadMark(JavaThread* current); + ~DumperThreadMark(); + }; + + static bool current_thread_is_vm_or_dumper() NOT_CDS_RETURN_(false); }; #endif // SHARE_CDS_CDSCONFIG_HPP diff --git a/src/hotspot/share/cds/cdsEnumKlass.cpp b/src/hotspot/share/cds/cdsEnumKlass.cpp index b77f2fd9d16..17438428c80 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.cpp +++ b/src/hotspot/share/cds/cdsEnumKlass.cpp @@ -39,7 +39,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) { Klass* k = orig_obj->klass(); Klass* buffered_k = ArchiveBuilder::get_buffered_klass(k); return k->is_instance_klass() && - InstanceKlass::cast(k)->java_super() == vmClasses::Enum_klass(); + InstanceKlass::cast(k)->is_enum_subclass(); } // -- Handling of Enum objects diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index 29e5af97c6a..fbc58e503ca 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -23,10 +23,14 @@ */ #include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsHeapVerifier.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/javaClasses.inline.hpp" +#include "classfile/moduleEntry.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "classfile/vmSymbols.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/resourceArea.hpp" @@ -38,12 +42,13 @@ #if INCLUDE_CDS_JAVA_HEAP // CDSHeapVerifier is used to check for problems where an archived object references a -// static field that may be reinitialized at runtime. In the following example, +// static field that may be get a different value at runtime. In the following example, // Foo.get.test() -// correctly returns true when CDS disabled, but incorrectly returns false when CDS is enabled. +// correctly returns true when CDS disabled, but incorrectly returns false when CDS is enabled, +// because the archived archivedFoo.bar value is different than Bar.bar. // // class Foo { -// final Foo archivedFoo; // this field is archived by CDS +// static final Foo archivedFoo; // this field is archived by CDS // Bar bar; // static { // CDS.initializeFromArchive(Foo.class); @@ -68,8 +73,8 @@ // [2] CDSHeapVerifier::do_entry() checks all the archived objects. None of them // should be in [1] // -// However, it's legal for *some* static fields to be references. This leads to the -// table of ADD_EXCL below. +// However, it's legal for *some* static fields to be referenced. The reasons are explained +// in the table of ADD_EXCL below. // // [A] In most of the cases, the module bootstrap code will update the static field // to point to part of the archived module graph. E.g., @@ -123,6 +128,12 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) ADD_EXCL("sun/invoke/util/ValueConversions", "ONE_INT", // E "ZERO_INT"); // E + if (CDSConfig::is_dumping_invokedynamic()) { + ADD_EXCL("java/lang/invoke/InvokerBytecodeGenerator", "MEMBERNAME_FACTORY", // D + "CD_Object_array", // E same as <...>ConstantUtils.CD_Object_array::CD_Object + "INVOKER_SUPER_DESC"); // E same as java.lang.constant.ConstantDescs::CD_Object + } + # undef ADD_EXCL ClassLoaderDataGraph::classes_do(this); @@ -130,15 +141,16 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) CDSHeapVerifier::~CDSHeapVerifier() { if (_problems > 0) { - log_warning(cds, heap)("Scanned %d objects. Found %d case(s) where " - "an object points to a static field that may be " - "reinitialized at runtime.", _archived_objs, _problems); + log_error(cds, heap)("Scanned %d objects. Found %d case(s) where " + "an object points to a static field that " + "may hold a different value at runtime.", _archived_objs, _problems); + MetaspaceShared::unrecoverable_writing_error(); } } class CDSHeapVerifier::CheckStaticFields : public FieldClosure { CDSHeapVerifier* _verifier; - InstanceKlass* _ik; + InstanceKlass* _ik; // The class whose static fields are being checked. const char** _exclusions; public: CheckStaticFields(CDSHeapVerifier* verifier, InstanceKlass* ik) @@ -151,9 +163,14 @@ public: return; } + if (fd->signature()->equals("Ljdk/internal/access/JavaLangAccess;")) { + // A few classes have static fields that point to SharedSecrets.getJavaLangAccess(). + // This object carries no state and we can create a new one in the production run. + return; + } oop static_obj_field = _ik->java_mirror()->obj_field(fd->offset()); if (static_obj_field != nullptr) { - Klass* klass = static_obj_field->klass(); + Klass* field_type = static_obj_field->klass(); if (_exclusions != nullptr) { for (const char** p = _exclusions; *p != nullptr; p++) { if (fd->name()->equals(*p)) { @@ -173,11 +190,35 @@ public: // This field points to an archived mirror. return; } - if (klass->has_archived_enum_objs()) { - // This klass is a subclass of java.lang.Enum. If any instance of this klass - // has been archived, we will archive all static fields of this klass. - // See HeapShared::initialize_enum_klass(). - return; + + if (field_type->is_instance_klass()) { + InstanceKlass* field_ik = InstanceKlass::cast(field_type); + if (field_ik->is_enum_subclass()) { + if (field_ik->has_archived_enum_objs() || ArchiveUtils::has_aot_initialized_mirror(field_ik)) { + // This field is an Enum. If any instance of this Enum has been archived, we will archive + // all static fields of this Enum as well. + return; + } + } + + if (field_ik->is_hidden() && ArchiveUtils::has_aot_initialized_mirror(field_ik)) { + // We have a static field in a core-library class that points to a method reference, which + // are safe to archive. + guarantee(_ik->module()->name() == vmSymbols::java_base(), "sanity"); + return; + } + + if (field_ik == vmClasses::MethodType_klass()) { + // The identity of MethodTypes are preserved between assembly phase and production runs + // (by MethodType::AOTHolder::archivedMethodTypes). No need to check. + return; + } + + if (field_ik == vmClasses::internal_Unsafe_klass() && ArchiveUtils::has_aot_initialized_mirror(field_ik)) { + // There's only a single instance of jdk/internal/misc/Unsafe, so all references will + // be pointing to this singleton, which has been archived. + return; + } } // This field *may* be initialized to a different value at runtime. Remember it @@ -188,7 +229,8 @@ public: }; // Remember all the static object fields of every class that are currently -// loaded. +// loaded. Later, we will check if any archived objects reference one of +// these fields. void CDSHeapVerifier::do_klass(Klass* k) { if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); @@ -200,6 +242,12 @@ void CDSHeapVerifier::do_klass(Klass* k) { return; } + if (ArchiveUtils::has_aot_initialized_mirror(ik)) { + // ik's won't be executed at runtime, the static fields in + // ik will carry their values to runtime. + return; + } + CheckStaticFields csf(this, ik); ik->do_local_static_fields(&csf); } @@ -221,10 +269,15 @@ inline bool CDSHeapVerifier::do_entry(oop& orig_obj, HeapShared::CachedOopInfo& // should be flagged by CDSHeapVerifier. return true; /* keep on iterating */ } + if (info->_holder->is_hidden()) { + return true; + } ResourceMark rm; + char* class_name = info->_holder->name()->as_C_string(); + char* field_name = info->_name->as_C_string(); LogStream ls(Log(cds, heap)::warning()); - ls.print_cr("Archive heap points to a static field that may be reinitialized at runtime:"); - ls.print_cr("Field: %s::%s", info->_holder->name()->as_C_string(), info->_name->as_C_string()); + ls.print_cr("Archive heap points to a static field that may hold a different value at runtime:"); + ls.print_cr("Field: %s::%s", class_name, field_name); ls.print("Value: "); orig_obj->print_on(&ls); ls.print_cr("--- trace begin ---"); @@ -266,6 +319,29 @@ void CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj) { } } +const char* static_field_name(oop mirror, oop field) { + Klass* k = java_lang_Class::as_Klass(mirror); + if (k->is_instance_klass()) { + for (JavaFieldStream fs(InstanceKlass::cast(k)); !fs.done(); fs.next()) { + if (fs.access_flags().is_static()) { + fieldDescriptor& fd = fs.field_descriptor(); + switch (fd.field_type()) { + case T_OBJECT: + case T_ARRAY: + if (mirror->obj_field(fd.offset()) == field) { + return fs.name()->as_C_string(); + } + break; + default: + break; + } + } + } + } + + return ""; +} + int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* info) { int level = 0; if (info->orig_referrer() != nullptr) { @@ -280,6 +356,9 @@ int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_fiel st->print("[%2d] ", level); orig_obj->print_address_on(st); st->print(" %s", k->internal_name()); + if (java_lang_Class::is_instance(orig_obj)) { + st->print(" (%s::%s)", java_lang_Class::as_Klass(orig_obj)->external_name(), static_field_name(orig_obj, orig_field)); + } if (orig_field != nullptr) { if (k->is_instance_klass()) { TraceFields clo(orig_obj, orig_field, st); diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index 828393a7b6b..38f5d8f46a6 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -94,6 +94,30 @@ "(2) always map at preferred address, and if unsuccessful, " \ "do not map the archive") \ range(0, 2) \ + \ + /*========== New "AOT" flags =========================================*/ \ + /* The following 3 flags are aliases of -Xshare:dump, */ \ + /* -XX:SharedArchiveFile=..., etc. See CDSConfig::check_flag_aliases()*/ \ + \ + product(ccstr, AOTMode, nullptr, \ + "Specifies how AOTCache should be created or used. Valid values " \ + "are: off, record, create, auto, on; the default is auto") \ + constraint(AOTModeConstraintFunc, AtParse) \ + \ + product(ccstr, AOTConfiguration, nullptr, \ + "Configuration information used by CreateAOTCache") \ + \ + product(ccstr, AOTCache, nullptr, \ + "Cache for improving start up and warm up") \ + \ + product(bool, AOTInvokeDynamicLinking, false, DIAGNOSTIC, \ + "AOT-link JVM_CONSTANT_InvokeDynamic entries in cached " \ + "ConstantPools") \ + \ + product(bool, AOTClassLinking, false, \ + "Load/link all archived classes for the boot/platform/app " \ + "loaders before application main") \ + // end of CDS_FLAGS DECLARE_FLAGS(CDS_FLAGS) diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index 694a179d7ee..57d14229795 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -23,9 +23,9 @@ */ #include "precompiled.hpp" +#include "cds/aotConstantPoolResolver.hpp" #include "cds/archiveUtils.hpp" #include "cds/classListParser.hpp" -#include "cds/classPrelinker.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/metaspaceShared.hpp" #include "cds/unregisteredClasses.hpp" @@ -46,6 +46,7 @@ #include "memory/resourceArea.hpp" #include "oops/constantPool.inline.hpp" #include "runtime/atomic.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" #include "runtime/javaCalls.hpp" @@ -69,9 +70,13 @@ ClassListParser::ClassListParser(const char* file, ParseMode parse_mode) : log_info(cds)("Parsing %s%s", file, parse_lambda_forms_invokers_only() ? " (lambda form invokers only)" : ""); if (!_file_input.is_open()) { - char errmsg[JVM_MAXPATHLEN]; - os::lasterror(errmsg, JVM_MAXPATHLEN); - vm_exit_during_initialization("Loading classlist failed", errmsg); + char reason[JVM_MAXPATHLEN]; + os::lasterror(reason, JVM_MAXPATHLEN); + vm_exit_during_initialization(err_msg("Loading %s %s failed", + FLAG_IS_DEFAULT(AOTConfiguration) ? + "classlist" : "AOTConfiguration file", + file), + reason); } _token = _line = nullptr; _interfaces = new (mtClass) GrowableArray(10, mtClass); @@ -594,6 +599,18 @@ void ClassListParser::resolve_indy(JavaThread* current, Symbol* class_name_symbo } void ClassListParser::resolve_indy_impl(Symbol* class_name_symbol, TRAPS) { + if (CDSConfig::is_dumping_invokedynamic()) { + // The CP entry for the invokedynamic instruction will be resolved. + // No need to do the following. + return; + } + + // This is an older CDS optimization: + // We store a pre-generated version of the lambda proxy class in the AOT cache, + // which will be loaded via JVM_LookupLambdaProxyClassFromArchive(). + // This eliminate dynamic class generation of the proxy class, but we still need to + // resolve the CP entry for the invokedynamic instruction, which may result in + // generation of LambdaForm classes. Handle class_loader(THREAD, SystemDictionary::java_system_loader()); Handle protection_domain; Klass* klass = SystemDictionary::resolve_or_fail(class_name_symbol, class_loader, protection_domain, true, CHECK); @@ -836,6 +853,8 @@ void ClassListParser::parse_constant_pool_tag() { case JVM_CONSTANT_InterfaceMethodref: preresolve_fmi = true; break; + case JVM_CONSTANT_InvokeDynamic: + preresolve_indy = true; break; default: constant_pool_resolution_warning("Unsupported constant pool index %d: %s (type=%d)", @@ -845,9 +864,12 @@ void ClassListParser::parse_constant_pool_tag() { } if (preresolve_class) { - ClassPrelinker::preresolve_class_cp_entries(THREAD, ik, &preresolve_list); + AOTConstantPoolResolver::preresolve_class_cp_entries(THREAD, ik, &preresolve_list); } if (preresolve_fmi) { - ClassPrelinker::preresolve_field_and_method_cp_entries(THREAD, ik, &preresolve_list); + AOTConstantPoolResolver::preresolve_field_and_method_cp_entries(THREAD, ik, &preresolve_list); + } + if (preresolve_indy) { + AOTConstantPoolResolver::preresolve_indy_cp_entries(THREAD, ik, &preresolve_list); } } diff --git a/src/hotspot/share/cds/classListWriter.cpp b/src/hotspot/share/cds/classListWriter.cpp index 1b9f589f1c5..78a6857ff73 100644 --- a/src/hotspot/share/cds/classListWriter.cpp +++ b/src/hotspot/share/cds/classListWriter.cpp @@ -128,9 +128,15 @@ void ClassListWriter::write_to_stream(const InstanceKlass* k, outputStream* stre } } - // filter out java/lang/invoke/BoundMethodHandle$Species... - if (cfs != nullptr && cfs->source() != nullptr && strcmp(cfs->source(), "_ClassSpecializer_generateConcreteSpeciesCode") == 0) { - return; + if (cfs != nullptr && cfs->source() != nullptr) { + if (strcmp(cfs->source(), "_ClassSpecializer_generateConcreteSpeciesCode") == 0) { + return; + } + + if (strncmp(cfs->source(), "__", 2) == 0) { + // generated class: __dynamic_proxy__, __JVM_LookupDefineClass__, etc + return; + } } { @@ -256,6 +262,18 @@ void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) { } if (cp->cache() != nullptr) { + Array* indy_entries = cp->cache()->resolved_indy_entries(); + if (indy_entries != nullptr) { + for (int i = 0; i < indy_entries->length(); i++) { + ResolvedIndyEntry* rie = indy_entries->adr_at(i); + int cp_index = rie->constant_pool_index(); + if (rie->is_resolved()) { + list.at_put(cp_index, true); + print = true; + } + } + } + Array* field_entries = cp->cache()->resolved_field_entries(); if (field_entries != nullptr) { for (int i = 0; i < field_entries->length(); i++) { @@ -274,7 +292,8 @@ void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) { ResolvedMethodEntry* rme = method_entries->adr_at(i); if (rme->is_resolved(Bytecodes::_invokevirtual) || rme->is_resolved(Bytecodes::_invokespecial) || - rme->is_resolved(Bytecodes::_invokeinterface)) { + rme->is_resolved(Bytecodes::_invokeinterface) || + rme->is_resolved(Bytecodes::_invokehandle)) { list.at_put(rme->constant_pool_index(), true); print = true; } @@ -291,7 +310,8 @@ void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) { assert(cp_tag.value() == JVM_CONSTANT_Class || cp_tag.value() == JVM_CONSTANT_Fieldref || cp_tag.value() == JVM_CONSTANT_Methodref|| - cp_tag.value() == JVM_CONSTANT_InterfaceMethodref, "sanity"); + cp_tag.value() == JVM_CONSTANT_InterfaceMethodref || + cp_tag.value() == JVM_CONSTANT_InvokeDynamic, "sanity"); stream->print(" %d", i); } } diff --git a/src/hotspot/share/cds/classPrelinker.cpp b/src/hotspot/share/cds/classPrelinker.cpp deleted file mode 100644 index 6b866bac995..00000000000 --- a/src/hotspot/share/cds/classPrelinker.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (c) 2022, 2024, 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. - * - */ - -#include "precompiled.hpp" -#include "cds/archiveBuilder.hpp" -#include "cds/cdsConfig.hpp" -#include "cds/classPrelinker.hpp" -#include "cds/regeneratedClasses.hpp" -#include "classfile/systemDictionary.hpp" -#include "classfile/systemDictionaryShared.hpp" -#include "classfile/vmClasses.hpp" -#include "interpreter/bytecodeStream.hpp" -#include "interpreter/interpreterRuntime.hpp" -#include "memory/resourceArea.hpp" -#include "oops/constantPool.inline.hpp" -#include "oops/instanceKlass.hpp" -#include "oops/klass.inline.hpp" -#include "runtime/handles.inline.hpp" - -ClassPrelinker::ClassesTable* ClassPrelinker::_processed_classes = nullptr; -ClassPrelinker::ClassesTable* ClassPrelinker::_vm_classes = nullptr; - -bool ClassPrelinker::is_vm_class(InstanceKlass* ik) { - return (_vm_classes->get(ik) != nullptr); -} - -void ClassPrelinker::add_one_vm_class(InstanceKlass* ik) { - bool created; - _vm_classes->put_if_absent(ik, &created); - if (created) { - InstanceKlass* super = ik->java_super(); - if (super != nullptr) { - add_one_vm_class(super); - } - Array* ifs = ik->local_interfaces(); - for (int i = 0; i < ifs->length(); i++) { - add_one_vm_class(ifs->at(i)); - } - } -} - -void ClassPrelinker::initialize() { - assert(_vm_classes == nullptr, "must be"); - _vm_classes = new (mtClass)ClassesTable(); - _processed_classes = new (mtClass)ClassesTable(); - for (auto id : EnumRange{}) { - add_one_vm_class(vmClasses::klass_at(id)); - } -} - -void ClassPrelinker::dispose() { - assert(_vm_classes != nullptr, "must be"); - delete _vm_classes; - delete _processed_classes; - _vm_classes = nullptr; - _processed_classes = nullptr; -} - -// Returns true if we CAN PROVE that cp_index will always resolve to -// the same information at both dump time and run time. This is a -// necessary (but not sufficient) condition for pre-resolving cp_index -// during CDS archive assembly. -bool ClassPrelinker::is_resolution_deterministic(ConstantPool* cp, int cp_index) { - assert(!is_in_archivebuilder_buffer(cp), "sanity"); - - if (cp->tag_at(cp_index).is_klass()) { - // We require cp_index to be already resolved. This is fine for now, are we - // currently archive only CP entries that are already resolved. - Klass* resolved_klass = cp->resolved_klass_at(cp_index); - return resolved_klass != nullptr && is_class_resolution_deterministic(cp->pool_holder(), resolved_klass); - } else if (cp->tag_at(cp_index).is_field() || - cp->tag_at(cp_index).is_method() || - cp->tag_at(cp_index).is_interface_method()) { - int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); - if (!cp->tag_at(klass_cp_index).is_klass()) { - // Not yet resolved - return false; - } - Klass* k = cp->resolved_klass_at(klass_cp_index); - if (!is_class_resolution_deterministic(cp->pool_holder(), k)) { - return false; - } - - if (!k->is_instance_klass()) { - // TODO: support non instance klasses as well. - return false; - } - - // Here, We don't check if this entry can actually be resolved to a valid Field/Method. - // This method should be called by the ConstantPool to check Fields/Methods that - // have already been successfully resolved. - return true; - } else { - return false; - } -} - -bool ClassPrelinker::is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class) { - assert(!is_in_archivebuilder_buffer(cp_holder), "sanity"); - assert(!is_in_archivebuilder_buffer(resolved_class), "sanity"); - - if (resolved_class->is_instance_klass()) { - InstanceKlass* ik = InstanceKlass::cast(resolved_class); - - if (!ik->is_shared() && SystemDictionaryShared::is_excluded_class(ik)) { - return false; - } - - if (cp_holder->is_subtype_of(ik)) { - // All super types of ik will be resolved in ik->class_loader() before - // ik is defined in this loader, so it's safe to archive the resolved klass reference. - return true; - } - - if (is_vm_class(ik)) { - if (ik->class_loader() != cp_holder->class_loader()) { - // At runtime, cp_holder() may not be able to resolve to the same - // ik. For example, a different version of ik may be defined in - // cp->pool_holder()'s loader using MethodHandles.Lookup.defineClass(). - return false; - } else { - return true; - } - } - } else if (resolved_class->is_objArray_klass()) { - Klass* elem = ObjArrayKlass::cast(resolved_class)->bottom_klass(); - if (elem->is_instance_klass()) { - return is_class_resolution_deterministic(cp_holder, InstanceKlass::cast(elem)); - } else if (elem->is_typeArray_klass()) { - return true; - } - } else if (resolved_class->is_typeArray_klass()) { - return true; - } - - return false; -} - -void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { - if (!ik->is_linked()) { - return; - } - bool first_time; - _processed_classes->put_if_absent(ik, &first_time); - if (!first_time) { - // We have already resolved the constants in class, so no need to do it again. - return; - } - - constantPoolHandle cp(THREAD, ik->constants()); - for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused - switch (cp->tag_at(cp_index).value()) { - case JVM_CONSTANT_String: - resolve_string(cp, cp_index, CHECK); // may throw OOM when interning strings. - break; - } - } -} - -// This works only for the boot/platform/app loaders -Klass* ClassPrelinker::find_loaded_class(Thread* current, oop class_loader, Symbol* name) { - HandleMark hm(current); - Handle h_loader(current, class_loader); - Klass* k = SystemDictionary::find_instance_or_array_klass(current, name, - h_loader, - Handle()); - if (k != nullptr) { - return k; - } - if (h_loader() == SystemDictionary::java_system_loader()) { - return find_loaded_class(current, SystemDictionary::java_platform_loader(), name); - } else if (h_loader() == SystemDictionary::java_platform_loader()) { - return find_loaded_class(current, nullptr, name); - } else { - assert(h_loader() == nullptr, "This function only works for boot/platform/app loaders %p %p %p", - cast_from_oop
(h_loader()), - cast_from_oop
(SystemDictionary::java_system_loader()), - cast_from_oop
(SystemDictionary::java_platform_loader())); - } - - return nullptr; -} - -Klass* ClassPrelinker::find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index) { - Symbol* name = cp->klass_name_at(class_cp_index); - return find_loaded_class(current, cp->pool_holder()->class_loader(), name); -} - -#if INCLUDE_CDS_JAVA_HEAP -void ClassPrelinker::resolve_string(constantPoolHandle cp, int cp_index, TRAPS) { - if (CDSConfig::is_dumping_heap()) { - int cache_index = cp->cp_to_object_index(cp_index); - ConstantPool::string_at_impl(cp, cp_index, cache_index, CHECK); - } -} -#endif - -void ClassPrelinker::preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { - if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data())) { - return; - } - - JavaThread* THREAD = current; - constantPoolHandle cp(THREAD, ik->constants()); - for (int cp_index = 1; cp_index < cp->length(); cp_index++) { - if (cp->tag_at(cp_index).value() == JVM_CONSTANT_UnresolvedClass) { - if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) { - // This class was not resolved during trial run. Don't attempt to resolve it. Otherwise - // the compiler may generate less efficient code. - continue; - } - if (find_loaded_class(current, cp(), cp_index) == nullptr) { - // Do not resolve any class that has not been loaded yet - continue; - } - Klass* resolved_klass = cp->klass_at(cp_index, THREAD); - if (HAS_PENDING_EXCEPTION) { - CLEAR_PENDING_EXCEPTION; // just ignore - } else { - log_trace(cds, resolve)("Resolved class [%3d] %s -> %s", cp_index, ik->external_name(), - resolved_klass->external_name()); - } - } - } -} - -void ClassPrelinker::preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { - JavaThread* THREAD = current; - constantPoolHandle cp(THREAD, ik->constants()); - if (cp->cache() == nullptr) { - return; - } - for (int i = 0; i < ik->methods()->length(); i++) { - Method* m = ik->methods()->at(i); - BytecodeStream bcs(methodHandle(THREAD, m)); - while (!bcs.is_last_bytecode()) { - bcs.next(); - Bytecodes::Code raw_bc = bcs.raw_code(); - switch (raw_bc) { - case Bytecodes::_getfield: - case Bytecodes::_putfield: - maybe_resolve_fmi_ref(ik, m, raw_bc, bcs.get_index_u2(), preresolve_list, THREAD); - if (HAS_PENDING_EXCEPTION) { - CLEAR_PENDING_EXCEPTION; // just ignore - } - break; - case Bytecodes::_invokespecial: - case Bytecodes::_invokevirtual: - case Bytecodes::_invokeinterface: - maybe_resolve_fmi_ref(ik, m, raw_bc, bcs.get_index_u2(), preresolve_list, THREAD); - if (HAS_PENDING_EXCEPTION) { - CLEAR_PENDING_EXCEPTION; // just ignore - } - break; - default: - break; - } - } - } -} - -void ClassPrelinker::maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, - GrowableArray* preresolve_list, TRAPS) { - methodHandle mh(THREAD, m); - constantPoolHandle cp(THREAD, ik->constants()); - HandleMark hm(THREAD); - int cp_index = cp->to_cp_index(raw_index, bc); - - if (cp->is_resolved(raw_index, bc)) { - return; - } - - if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) { - // This field wasn't resolved during the trial run. Don't attempt to resolve it. Otherwise - // the compiler may generate less efficient code. - return; - } - - int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); - if (find_loaded_class(THREAD, cp(), klass_cp_index) == nullptr) { - // Do not resolve any field/methods from a class that has not been loaded yet. - return; - } - - Klass* resolved_klass = cp->klass_ref_at(raw_index, bc, CHECK); - - switch (bc) { - case Bytecodes::_getfield: - case Bytecodes::_putfield: - InterpreterRuntime::resolve_get_put(bc, raw_index, mh, cp, false /*initialize_holder*/, CHECK); - break; - - case Bytecodes::_invokevirtual: - case Bytecodes::_invokespecial: - case Bytecodes::_invokeinterface: - InterpreterRuntime::cds_resolve_invoke(bc, raw_index, cp, CHECK); - break; - - default: - ShouldNotReachHere(); - } - - if (log_is_enabled(Trace, cds, resolve)) { - ResourceMark rm(THREAD); - bool resolved = cp->is_resolved(raw_index, bc); - Symbol* name = cp->name_ref_at(raw_index, bc); - Symbol* signature = cp->signature_ref_at(raw_index, bc); - log_trace(cds, resolve)("%s %s [%3d] %s -> %s.%s:%s", - (resolved ? "Resolved" : "Failed to resolve"), - Bytecodes::name(bc), cp_index, ik->external_name(), - resolved_klass->external_name(), - name->as_C_string(), signature->as_C_string()); - } -} - -#ifdef ASSERT -bool ClassPrelinker::is_in_archivebuilder_buffer(address p) { - if (!Thread::current()->is_VM_thread() || ArchiveBuilder::current() == nullptr) { - return false; - } else { - return ArchiveBuilder::current()->is_in_buffer_space(p); - } -} -#endif diff --git a/src/hotspot/share/cds/dumpAllocStats.cpp b/src/hotspot/share/cds/dumpAllocStats.cpp index 25351f78d84..e88a77de7ad 100644 --- a/src/hotspot/share/cds/dumpAllocStats.cpp +++ b/src/hotspot/share/cds/dumpAllocStats.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" #include "cds/dumpAllocStats.hpp" #include "logging/log.hpp" #include "logging/logMessage.hpp" @@ -114,6 +115,12 @@ void DumpAllocStats::print_stats(int ro_all, int rw_all) { _num_method_cp_entries, _num_method_cp_entries_archived, percent_of(_num_method_cp_entries_archived, _num_method_cp_entries), _num_method_cp_entries_reverted); + msg.info("Indy CP entries = %6d, archived = %6d (%5.1f%%), reverted = %6d", + _num_indy_cp_entries, _num_indy_cp_entries_archived, + percent_of(_num_indy_cp_entries_archived, _num_indy_cp_entries), + _num_indy_cp_entries_reverted); + msg.info("Platform loader initiated classes = %5d", AOTClassLinker::num_platform_initiated_classes()); + msg.info("App loader initiated classes = %5d", AOTClassLinker::num_app_initiated_classes()); } #ifdef ASSERT diff --git a/src/hotspot/share/cds/dumpAllocStats.hpp b/src/hotspot/share/cds/dumpAllocStats.hpp index 2ff81c52392..7d651320e6f 100644 --- a/src/hotspot/share/cds/dumpAllocStats.hpp +++ b/src/hotspot/share/cds/dumpAllocStats.hpp @@ -68,6 +68,9 @@ public: int _num_field_cp_entries; int _num_field_cp_entries_archived; int _num_field_cp_entries_reverted; + int _num_indy_cp_entries; + int _num_indy_cp_entries_archived; + int _num_indy_cp_entries_reverted; int _num_klass_cp_entries; int _num_klass_cp_entries_archived; int _num_klass_cp_entries_reverted; @@ -84,6 +87,9 @@ public: _num_field_cp_entries = 0; _num_field_cp_entries_archived = 0; _num_field_cp_entries_reverted = 0; + _num_indy_cp_entries = 0; + _num_indy_cp_entries_archived = 0; + _num_indy_cp_entries_reverted = 0; _num_klass_cp_entries = 0; _num_klass_cp_entries_archived = 0; _num_klass_cp_entries_reverted = 0; @@ -122,6 +128,12 @@ public: _num_field_cp_entries_reverted += reverted ? 1 : 0; } + void record_indy_cp_entry(bool archived, bool reverted) { + _num_indy_cp_entries ++; + _num_indy_cp_entries_archived += archived ? 1 : 0; + _num_indy_cp_entries_reverted += reverted ? 1 : 0; + } + void record_klass_cp_entry(bool archived, bool reverted) { _num_klass_cp_entries ++; _num_klass_cp_entries_archived += archived ? 1 : 0; diff --git a/src/hotspot/share/cds/dumpTimeClassInfo.hpp b/src/hotspot/share/cds/dumpTimeClassInfo.hpp index 5ba79e54c79..c060601f1fb 100644 --- a/src/hotspot/share/cds/dumpTimeClassInfo.hpp +++ b/src/hotspot/share/cds/dumpTimeClassInfo.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -42,7 +42,8 @@ class DumpTimeClassInfo: public CHeapObj { bool _excluded; bool _is_early_klass; bool _has_checked_exclusion; - + bool _is_required_hidden_class; + bool _has_scanned_constant_pool; class DTLoaderConstraint { Symbol* _name; char _loader_type1; @@ -137,6 +138,8 @@ public: _failed_verification = false; _is_archived_lambda_proxy = false; _has_checked_exclusion = false; + _is_required_hidden_class = false; + _has_scanned_constant_pool = false; _id = -1; _clsfile_size = -1; _clsfile_crc32 = -1; @@ -214,6 +217,11 @@ public: InstanceKlass* nest_host() const { return _nest_host; } void set_nest_host(InstanceKlass* nest_host) { _nest_host = nest_host; } + bool is_required_hidden_class() const { return _is_required_hidden_class; } + void set_is_required_hidden_class() { _is_required_hidden_class = true; } + bool has_scanned_constant_pool() const { return _has_scanned_constant_pool; } + void set_has_scanned_constant_pool() { _has_scanned_constant_pool = true; } + size_t runtime_info_bytesize() const; }; diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index 04c60b89580..f102282f682 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -23,12 +23,12 @@ */ #include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapWriter.hpp" #include "cds/archiveUtils.inline.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" -#include "cds/classPrelinker.hpp" #include "cds/dynamicArchive.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/classLoader.hpp" @@ -47,8 +47,8 @@ #include "runtime/arguments.hpp" #include "runtime/os.hpp" #include "runtime/sharedRuntime.hpp" -#include "runtime/vmThread.hpp" #include "runtime/vmOperations.hpp" +#include "runtime/vmThread.hpp" #include "utilities/align.hpp" #include "utilities/bitMap.inline.hpp" @@ -112,7 +112,7 @@ public: // Block concurrent class unloading from changing the _dumptime_table MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); - SystemDictionaryShared::check_excluded_classes(); + SystemDictionaryShared::find_all_archivable_classes(); if (SystemDictionaryShared::is_dumptime_table_empty()) { log_warning(cds, dynamic)("There is no class to be included in the dynamic archive."); @@ -135,6 +135,11 @@ public: verify_estimate_size(_estimated_metaspaceobj_bytes, "MetaspaceObjs"); + sort_methods(); + + log_info(cds)("Make classes shareable"); + make_klasses_shareable(); + char* serialized_data; { // Write the symbol table and system dictionaries to the RO space. @@ -147,6 +152,7 @@ public: ArchiveBuilder::OtherROAllocMark mark; SystemDictionaryShared::write_to_archive(false); DynamicArchive::dump_array_klasses(); + AOTClassLinker::write_to_archive(); serialized_data = ro_region()->top(); WriteClosure wc(ro_region()); @@ -155,11 +161,6 @@ public: verify_estimate_size(_estimated_hashtable_bytes, "Hashtables"); - sort_methods(); - - log_info(cds)("Make classes shareable"); - make_klasses_shareable(); - log_info(cds)("Adjust lambda proxy class dictionary"); SystemDictionaryShared::adjust_lambda_proxy_class_dictionary(); @@ -234,7 +235,7 @@ void DynamicArchiveBuilder::release_header() { void DynamicArchiveBuilder::post_dump() { ArchivePtrMarker::reset_map_and_vs(); - ClassPrelinker::dispose(); + AOTClassLinker::dispose(); } void DynamicArchiveBuilder::sort_methods() { @@ -497,6 +498,7 @@ void DynamicArchive::check_for_dynamic_dump() { void DynamicArchive::dump_at_exit(JavaThread* current, const char* archive_name) { ExceptionMark em(current); ResourceMark rm(current); + CDSConfig::DumperThreadMark dumper_thread_mark(current); if (!CDSConfig::is_dumping_dynamic_archive() || archive_name == nullptr) { return; @@ -526,6 +528,7 @@ void DynamicArchive::dump_at_exit(JavaThread* current, const char* archive_name) // This is called by "jcmd VM.cds dynamic_dump" void DynamicArchive::dump_for_jcmd(const char* archive_name, TRAPS) { + CDSConfig::DumperThreadMark dumper_thread_mark(THREAD); assert(CDSConfig::is_using_archive() && RecordDynamicDumpInfo, "already checked in arguments.cpp"); assert(ArchiveClassesAtExit == nullptr, "already checked in arguments.cpp"); assert(CDSConfig::is_dumping_dynamic_archive(), "already checked by check_for_dynamic_dump() during VM startup"); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 5e88b3c47e4..00d8fba4411 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -223,7 +223,9 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, } _max_heap_size = MaxHeapSize; _use_optimized_module_handling = CDSConfig::is_using_optimized_module_handling(); + _has_aot_linked_classes = CDSConfig::is_dumping_aot_linked_classes(); _has_full_module_graph = CDSConfig::is_dumping_full_module_graph(); + _has_archived_invokedynamic = CDSConfig::is_dumping_invokedynamic(); // The following fields are for sanity checks for whether this archive // will function correctly with this JVM and the bootclasspath it's @@ -313,6 +315,8 @@ void FileMapHeader::print(outputStream* st) { st->print_cr("- allow_archiving_with_java_agent:%d", _allow_archiving_with_java_agent); st->print_cr("- use_optimized_module_handling: %d", _use_optimized_module_handling); st->print_cr("- has_full_module_graph %d", _has_full_module_graph); + st->print_cr("- has_aot_linked_classes %d", _has_aot_linked_classes); + st->print_cr("- has_archived_invokedynamic %d", _has_archived_invokedynamic); } void SharedClassPathEntry::init_as_non_existent(const char* path, TRAPS) { @@ -1060,7 +1064,9 @@ bool FileMapInfo::validate_shared_path_table() { } } - validate_non_existent_class_paths(); + if (!validate_non_existent_class_paths()) { + return false; + } _validating_shared_path_table = false; @@ -1076,7 +1082,7 @@ bool FileMapInfo::validate_shared_path_table() { return true; } -void FileMapInfo::validate_non_existent_class_paths() { +bool FileMapInfo::validate_non_existent_class_paths() { // All of the recorded non-existent paths came from the Class-Path: attribute from the JAR // files on the app classpath. If any of these are found to exist during runtime, // it will change how classes are loading for the app loader. For safety, disable @@ -1089,11 +1095,19 @@ void FileMapInfo::validate_non_existent_class_paths() { i++) { SharedClassPathEntry* ent = shared_path(i); if (!ent->check_non_existent()) { - log_warning(cds)("Archived non-system classes are disabled because the " - "file %s exists", ent->name()); - header()->set_has_platform_or_app_classes(false); + if (header()->has_aot_linked_classes()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used because the " + "file %s exists", ent->name()); + return false; + } else { + log_warning(cds)("Archived non-system classes are disabled because the " + "file %s exists", ent->name()); + header()->set_has_platform_or_app_classes(false); + } } } + + return true; } // A utility class for reading/validating the GenericCDSFileMapHeader portion of @@ -2074,7 +2088,16 @@ void FileMapInfo::map_or_load_heap_region() { } if (!success) { - CDSConfig::stop_using_full_module_graph(); + if (CDSConfig::is_using_aot_linked_classes()) { + // It's too late to recover -- we have already committed to use the archived metaspace objects, but + // the archived heap objects cannot be loaded, so we don't have the archived FMG to guarantee that + // all AOT-linked classes are visible. + // + // We get here because the heap is too small. The app will fail anyway. So let's quit. + MetaspaceShared::unrecoverable_loading_error("CDS archive has aot-linked classes but the archived " + "heap objects cannot be loaded. Try increasing your heap size."); + } + CDSConfig::stop_using_full_module_graph("archive heap loading failed"); } } @@ -2429,6 +2452,34 @@ bool FileMapInfo::initialize() { return true; } +bool FileMapInfo::validate_aot_class_linking() { + // These checks need to be done after FileMapInfo::initialize(), which gets called before Universe::heap() + // is available. + if (header()->has_aot_linked_classes()) { + CDSConfig::set_has_aot_linked_classes(true); + if (JvmtiExport::should_post_class_file_load_hook()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used when JVMTI ClassFileLoadHook is in use."); + return false; + } + if (JvmtiExport::has_early_vmstart_env()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used when JVMTI early vm start is in use."); + return false; + } + if (!CDSConfig::is_using_full_module_graph()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used."); + return false; + } + + const char* prop = Arguments::get_property("java.security.manager"); + if (prop != nullptr && strcmp(prop, "disallow") != 0) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used with -Djava.security.manager=%s.", prop); + return false; + } + } + + return true; +} + // The 2 core spaces are RW->RO FileMapRegion* FileMapInfo::first_core_region() const { return region_at(MetaspaceShared::rw); @@ -2478,6 +2529,11 @@ bool FileMapHeader::validate() { // header data const char* prop = Arguments::get_property("java.system.class.loader"); if (prop != nullptr) { + if (has_aot_linked_classes()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used when the " + "java.system.class.loader property is specified."); + return false; + } log_warning(cds)("Archived non-system classes are disabled because the " "java.system.class.loader property is specified (value = \"%s\"). " "To use archived non-system classes, this property must not be set", prop); @@ -2542,9 +2598,15 @@ bool FileMapHeader::validate() { log_info(cds)("optimized module handling: disabled because archive was created without optimized module handling"); } - if (is_static() && !_has_full_module_graph) { + if (is_static()) { // Only the static archive can contain the full module graph. - CDSConfig::stop_using_full_module_graph("archive was created without full module graph"); + if (!_has_full_module_graph) { + CDSConfig::stop_using_full_module_graph("archive was created without full module graph"); + } + + if (_has_archived_invokedynamic) { + CDSConfig::set_has_archived_invokedynamic(); + } } return true; diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 6a15ff03c3a..6319c51f1ce 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -228,7 +228,9 @@ private: bool _allow_archiving_with_java_agent; // setting of the AllowArchivingWithJavaAgent option bool _use_optimized_module_handling;// No module-relation VM options were specified, so we can skip // some expensive operations. + bool _has_aot_linked_classes; // Was the CDS archive created with -XX:+AOTClassLinking bool _has_full_module_graph; // Does this CDS archive contain the full archived module graph? + bool _has_archived_invokedynamic; // Does the archive have aot-linked invokedynamic CP entries? HeapRootSegments _heap_root_segments; // Heap root segments info size_t _heap_oopmap_start_pos; // The first bit in the oopmap corresponds to this position in the heap. size_t _heap_ptrmap_start_pos; // The first bit in the ptrmap corresponds to this position in the heap. @@ -273,6 +275,7 @@ public: char* mapped_base_address() const { return _mapped_base_address; } bool has_platform_or_app_classes() const { return _has_platform_or_app_classes; } bool has_non_jar_in_classpath() const { return _has_non_jar_in_classpath; } + bool has_aot_linked_classes() const { return _has_aot_linked_classes; } bool compressed_oops() const { return _compressed_oops; } bool compressed_class_pointers() const { return _compressed_class_ptrs; } int narrow_klass_pointer_bits() const { return _narrow_klass_pointer_bits; } @@ -492,7 +495,8 @@ public: static void check_nonempty_dir_in_shared_path_table(); bool check_module_paths(); bool validate_shared_path_table(); - void validate_non_existent_class_paths(); + bool validate_non_existent_class_paths(); + bool validate_aot_class_linking(); static void set_shared_path_table(FileMapInfo* info) { _shared_path_table = info->header()->shared_path_table(); } diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 22040448770..3a7ba936bb0 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapLoader.hpp" #include "cds/archiveHeapWriter.hpp" @@ -97,7 +98,7 @@ size_t HeapShared::_total_obj_size; #define ARCHIVE_TEST_FIELD_NAME "archivedObjects" static Array* _archived_ArchiveHeapTestClass = nullptr; static const char* _test_class_name = nullptr; -static const Klass* _test_class = nullptr; +static Klass* _test_class = nullptr; static const ArchivedKlassSubGraphInfoRecord* _test_class_record = nullptr; #endif @@ -119,6 +120,7 @@ static ArchivableStaticFieldInfo archive_subgraph_entry_fields[] = { {"java/lang/ModuleLayer", "EMPTY_LAYER"}, {"java/lang/module/Configuration", "EMPTY_CONFIGURATION"}, {"jdk/internal/math/FDBigInteger", "archivedCaches"}, + #ifndef PRODUCT {nullptr, nullptr}, // Extra slot for -XX:ArchiveHeapTestClass #endif @@ -133,7 +135,8 @@ static ArchivableStaticFieldInfo fmg_archive_subgraph_entry_fields[] = { {nullptr, nullptr}, }; -KlassSubGraphInfo* HeapShared::_default_subgraph_info; +KlassSubGraphInfo* HeapShared::_dump_time_special_subgraph; +ArchivedKlassSubGraphInfoRecord* HeapShared::_run_time_special_subgraph; GrowableArrayCHeap* HeapShared::_pending_roots = nullptr; GrowableArrayCHeap* HeapShared::_root_segments; int HeapShared::_root_segment_max_size_elems; @@ -298,6 +301,7 @@ bool HeapShared::archive_object(oop obj) { if (ArchiveHeapWriter::is_too_large_to_archive(obj->size())) { log_debug(cds, heap)("Cannot archive, object (" PTR_FORMAT ") is too large: " SIZE_FORMAT, p2i(obj), obj->size()); + debug_trace(); return false; } else { count_allocation(obj->size()); @@ -309,8 +313,19 @@ bool HeapShared::archive_object(oop obj) { if (log_is_enabled(Debug, cds, heap)) { ResourceMark rm; - log_debug(cds, heap)("Archived heap object " PTR_FORMAT " : %s", - p2i(obj), obj->klass()->external_name()); + LogTarget(Debug, cds, heap) log; + LogStream out(log); + out.print("Archived heap object " PTR_FORMAT " : %s ", + p2i(obj), obj->klass()->external_name()); + if (java_lang_Class::is_instance(obj)) { + Klass* k = java_lang_Class::as_Klass(obj); + if (k != nullptr) { + out.print("%s", k->external_name()); + } else { + out.print("primitive"); + } + } + out.cr(); } if (java_lang_Module::is_instance(obj) && Modules::check_archived_module_oop(obj)) { @@ -371,6 +386,31 @@ void HeapShared::init_scratch_objects(TRAPS) { _scratch_references_table = new (mtClass)MetaspaceObjToOopHandleTable(); } +// Given java_mirror that represents a (primitive or reference) type T, +// return the "scratch" version that represents the same type T. +// Note that if java_mirror will be returned if it's already a +// scratch mirror. +// +// See java_lang_Class::create_scratch_mirror() for more info. +oop HeapShared::scratch_java_mirror(oop java_mirror) { + assert(java_lang_Class::is_instance(java_mirror), "must be"); + + for (int i = T_BOOLEAN; i < T_VOID+1; i++) { + BasicType bt = (BasicType)i; + if (!is_reference_type(bt)) { + if (_scratch_basic_type_mirrors[i].resolve() == java_mirror) { + return java_mirror; + } + } + } + + if (java_lang_Class::is_primitive(java_mirror)) { + return scratch_java_mirror(java_lang_Class::as_BasicType(java_mirror)); + } else { + return scratch_java_mirror(java_lang_Class::as_Klass(java_mirror)); + } +} + oop HeapShared::scratch_java_mirror(BasicType t) { assert((uint)t < T_VOID+1, "range check"); assert(!is_reference_type(t), "sanity"); @@ -399,13 +439,134 @@ void HeapShared::remove_scratch_objects(Klass* k) { } } +//TODO: we eventually want a more direct test for these kinds of things. +//For example the JVM could record some bit of context from the creation +//of the klass, such as who called the hidden class factory. Using +//string compares on names is fragile and will break as soon as somebody +//changes the names in the JDK code. See discussion in JDK-8342481 for +//related ideas about marking AOT-related classes. +bool HeapShared::is_lambda_form_klass(InstanceKlass* ik) { + return ik->is_hidden() && + (ik->name()->starts_with("java/lang/invoke/LambdaForm$MH+") || + ik->name()->starts_with("java/lang/invoke/LambdaForm$DMH+") || + ik->name()->starts_with("java/lang/invoke/LambdaForm$BMH+") || + ik->name()->starts_with("java/lang/invoke/LambdaForm$VH+")); +} + +bool HeapShared::is_lambda_proxy_klass(InstanceKlass* ik) { + return ik->is_hidden() && (ik->name()->index_of_at(0, "$$Lambda+", 9) > 0); +} + +bool HeapShared::is_string_concat_klass(InstanceKlass* ik) { + return ik->is_hidden() && ik->name()->starts_with("java/lang/String$$StringConcat"); +} + +bool HeapShared::is_archivable_hidden_klass(InstanceKlass* ik) { + return CDSConfig::is_dumping_invokedynamic() && + (is_lambda_form_klass(ik) || is_lambda_proxy_klass(ik) || is_string_concat_klass(ik)); +} + +void HeapShared::copy_aot_initialized_mirror(Klass* orig_k, oop orig_mirror, oop m) { + assert(orig_k->is_instance_klass(), "sanity"); + InstanceKlass* ik = InstanceKlass::cast(orig_k); + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(ik); + + assert(ik->is_initialized(), "must be"); + + int nfields = 0; + for (JavaFieldStream fs(ik); !fs.done(); fs.next()) { + if (fs.access_flags().is_static()) { + fieldDescriptor& fd = fs.field_descriptor(); + int offset = fd.offset(); + switch (fd.field_type()) { + case T_OBJECT: + case T_ARRAY: + m->obj_field_put(offset, orig_mirror->obj_field(offset)); + break; + case T_BOOLEAN: + m->bool_field_put(offset, orig_mirror->bool_field(offset)); + break; + case T_BYTE: + m->byte_field_put(offset, orig_mirror->byte_field(offset)); + break; + case T_SHORT: + m->short_field_put(offset, orig_mirror->short_field(offset)); + break; + case T_CHAR: + m->char_field_put(offset, orig_mirror->char_field(offset)); + break; + case T_INT: + m->int_field_put(offset, orig_mirror->int_field(offset)); + break; + case T_LONG: + m->long_field_put(offset, orig_mirror->long_field(offset)); + break; + case T_FLOAT: + m->float_field_put(offset, orig_mirror->float_field(offset)); + break; + case T_DOUBLE: + m->double_field_put(offset, orig_mirror->double_field(offset)); + break; + default: + ShouldNotReachHere(); + } + nfields ++; + } + } + + java_lang_Class::set_class_data(m, java_lang_Class::class_data(orig_mirror)); + + // Class::reflectData use SoftReference, which cannot be archived. Set it + // to null and it will be recreated at runtime. + java_lang_Class::set_reflection_data(m, nullptr); + + if (log_is_enabled(Info, cds, init)) { + ResourceMark rm; + log_debug(cds, init)("copied %3d field(s) in aot-initialized mirror %s%s", nfields, ik->external_name(), + ik->is_hidden() ? " (hidden)" : ""); + } +} + +static void copy_java_mirror_hashcode(oop orig_mirror, oop scratch_m) { + // We need to retain the identity_hash, because it may have been used by some hashtables + // in the shared heap. + if (!orig_mirror->fast_no_hash_check()) { + intptr_t src_hash = orig_mirror->identity_hash(); + if (UseCompactObjectHeaders) { + narrowKlass nk = CompressedKlassPointers::encode(orig_mirror->klass()); + scratch_m->set_mark(markWord::prototype().set_narrow_klass(nk).copy_set_hash(src_hash)); + } else { + scratch_m->set_mark(markWord::prototype().copy_set_hash(src_hash)); + } + assert(scratch_m->mark().is_unlocked(), "sanity"); + + DEBUG_ONLY(intptr_t archived_hash = scratch_m->identity_hash()); + assert(src_hash == archived_hash, "Different hash codes: original " INTPTR_FORMAT ", archived " INTPTR_FORMAT, src_hash, archived_hash); + } +} + +static objArrayOop get_archived_resolved_references(InstanceKlass* src_ik) { + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(src_ik); + if (buffered_ik->is_shared_boot_class() || + buffered_ik->is_shared_platform_class() || + buffered_ik->is_shared_app_class()) { + objArrayOop rr = src_ik->constants()->resolved_references_or_null(); + if (rr != nullptr && !ArchiveHeapWriter::is_too_large_to_archive(rr)) { + return HeapShared::scratch_resolved_references(src_ik->constants()); + } + } + return nullptr; +} + void HeapShared::archive_java_mirrors() { for (int i = T_BOOLEAN; i < T_VOID+1; i++) { BasicType bt = (BasicType)i; if (!is_reference_type(bt)) { + oop orig_mirror = Universe::java_mirror(bt); oop m = _scratch_basic_type_mirrors[i].resolve(); assert(m != nullptr, "sanity"); - bool success = archive_reachable_objects_from(1, _default_subgraph_info, m); + copy_java_mirror_hashcode(orig_mirror, m); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, m); assert(success, "sanity"); log_trace(cds, heap, mirror)( @@ -418,12 +579,23 @@ void HeapShared::archive_java_mirrors() { GrowableArray* klasses = ArchiveBuilder::current()->klasses(); assert(klasses != nullptr, "sanity"); + for (int i = 0; i < klasses->length(); i++) { Klass* orig_k = klasses->at(i); + oop orig_mirror = orig_k->java_mirror(); + oop m = scratch_java_mirror(orig_k); + if (m != nullptr) { + copy_java_mirror_hashcode(orig_mirror, m); + } + } + + for (int i = 0; i < klasses->length(); i++) { + Klass* orig_k = klasses->at(i); + oop orig_mirror = orig_k->java_mirror(); oop m = scratch_java_mirror(orig_k); if (m != nullptr) { Klass* buffered_k = ArchiveBuilder::get_buffered_klass(orig_k); - bool success = archive_reachable_objects_from(1, _default_subgraph_info, m); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, m); guarantee(success, "scratch mirrors must point to only archivable objects"); buffered_k->set_archived_java_mirror(append_root(m)); ResourceMark rm; @@ -434,9 +606,9 @@ void HeapShared::archive_java_mirrors() { // archive the resolved_referenes array if (buffered_k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(buffered_k); - oop rr = ik->constants()->prepare_resolved_references_for_archiving(); - if (rr != nullptr && !ArchiveHeapWriter::is_too_large_to_archive(rr)) { - bool success = HeapShared::archive_reachable_objects_from(1, _default_subgraph_info, rr); + objArrayOop rr = get_archived_resolved_references(InstanceKlass::cast(orig_k)); + if (rr != nullptr) { + bool success = HeapShared::archive_reachable_objects_from(1, _dump_time_special_subgraph, rr); assert(success, "must be"); int root_index = append_root(rr); ik->constants()->cache()->set_archived_references(root_index); @@ -448,7 +620,7 @@ void HeapShared::archive_java_mirrors() { void HeapShared::archive_strings() { oop shared_strings_array = StringTable::init_shared_table(_dumped_interned_strings); - bool success = archive_reachable_objects_from(1, _default_subgraph_info, shared_strings_array); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, shared_strings_array); // We must succeed because: // - _dumped_interned_strings do not contain any large strings. // - StringTable::init_shared_table() doesn't create any large arrays. @@ -457,7 +629,7 @@ void HeapShared::archive_strings() { } int HeapShared::archive_exception_instance(oop exception) { - bool success = archive_reachable_objects_from(1, _default_subgraph_info, exception); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, exception); assert(success, "sanity"); return append_root(exception); } @@ -466,6 +638,8 @@ void HeapShared::mark_native_pointers(oop orig_obj) { if (java_lang_Class::is_instance(orig_obj)) { ArchiveHeapWriter::mark_native_pointer(orig_obj, java_lang_Class::klass_offset()); ArchiveHeapWriter::mark_native_pointer(orig_obj, java_lang_Class::array_klass_offset()); + } else if (java_lang_invoke_ResolvedMethodName::is_instance(orig_obj)) { + ArchiveHeapWriter::mark_native_pointer(orig_obj, java_lang_invoke_ResolvedMethodName::vmtarget_offset()); } } @@ -482,11 +656,121 @@ void HeapShared::set_has_native_pointers(oop src_obj) { info->set_has_native_pointers(); } +void HeapShared::start_finding_required_hidden_classes() { + if (!CDSConfig::is_dumping_invokedynamic()) { + return; + } + NoSafepointVerifier nsv; + + init_seen_objects_table(); + + // We first scan the objects that are known to be archived (from the archive_subgraph + // tables) + find_required_hidden_classes_helper(archive_subgraph_entry_fields); + if (CDSConfig::is_dumping_full_module_graph()) { + find_required_hidden_classes_helper(fmg_archive_subgraph_entry_fields); + } + + // Later, SystemDictionaryShared::find_all_archivable_classes_impl() will start + // scanning the constant pools of all classes that it decides to archive. +} + +void HeapShared::end_finding_required_hidden_classes() { + if (!CDSConfig::is_dumping_invokedynamic()) { + return; + } + NoSafepointVerifier nsv; + + delete_seen_objects_table(); +} + +void HeapShared::find_required_hidden_classes_helper(ArchivableStaticFieldInfo fields[]) { + if (!CDSConfig::is_dumping_heap()) { + return; + } + for (int i = 0; fields[i].valid(); i++) { + ArchivableStaticFieldInfo* f = &fields[i]; + InstanceKlass* k = f->klass; + oop m = k->java_mirror(); + oop o = m->obj_field(f->offset); + if (o != nullptr) { + find_required_hidden_classes_in_object(o); + } + } +} + +class HeapShared::FindRequiredHiddenClassesOopClosure: public BasicOopIterateClosure { + GrowableArray _stack; + template void do_oop_work(T *p) { + // Recurse on a GrowableArray to avoid overflowing the C stack. + oop o = RawAccess<>::oop_load(p); + if (o != nullptr) { + _stack.append(o); + } + } + + public: + + void do_oop(narrowOop *p) { FindRequiredHiddenClassesOopClosure::do_oop_work(p); } + void do_oop( oop *p) { FindRequiredHiddenClassesOopClosure::do_oop_work(p); } + + FindRequiredHiddenClassesOopClosure(oop o) { + _stack.append(o); + } + oop pop() { + if (_stack.length() == 0) { + return nullptr; + } else { + return _stack.pop(); + } + } +}; + +static void mark_required_if_hidden_class(Klass* k) { + if (k != nullptr && k->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(k); + if (ik->is_hidden()) { + SystemDictionaryShared::mark_required_hidden_class(ik); + } + } +} + + +void HeapShared::find_required_hidden_classes_in_object(oop root) { + ResourceMark rm; + FindRequiredHiddenClassesOopClosure c(root); + oop o; + while ((o = c.pop()) != nullptr) { + if (!has_been_seen_during_subgraph_recording(o)) { + set_has_been_seen_during_subgraph_recording(o); + + // Mark the klass of this object + mark_required_if_hidden_class(o->klass()); + + // For special objects, mark the klass that they contain information about. + // - a Class that refers to an hidden class + // - a ResolvedMethodName that refers to a method declared in a hidden class + if (java_lang_Class::is_instance(o)) { + mark_required_if_hidden_class(java_lang_Class::as_Klass(o)); + } else if (java_lang_invoke_ResolvedMethodName::is_instance(o)) { + Method* m = java_lang_invoke_ResolvedMethodName::vmtarget(o); + if (m != nullptr) { + mark_required_if_hidden_class(m->method_holder()); + } + } + + o->oop_iterate(&c); + } + } +} + void HeapShared::archive_objects(ArchiveHeapInfo *heap_info) { { NoSafepointVerifier nsv; - _default_subgraph_info = init_subgraph_info(vmClasses::Object_klass(), false); + // The special subgraph doesn't belong to any class. We use Object_klass() here just + // for convenience. + _dump_time_special_subgraph = init_subgraph_info(vmClasses::Object_klass(), false); // Cache for recording where the archived objects are copied to create_archived_object_cache(); @@ -501,7 +785,7 @@ void HeapShared::archive_objects(ArchiveHeapInfo *heap_info) { copy_objects(); CDSHeapVerifier::verify(); - check_default_subgraph_classes(); + check_special_subgraph_classes(); } ArchiveHeapWriter::write(_pending_roots, heap_info); @@ -513,7 +797,7 @@ void HeapShared::copy_interned_strings() { auto copier = [&] (oop s, bool value_ignored) { assert(s != nullptr, "sanity"); assert(!ArchiveHeapWriter::is_string_too_large_to_archive(s), "large strings must have been filtered"); - bool success = archive_reachable_objects_from(1, _default_subgraph_info, s); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, s); assert(success, "must be"); // Prevent string deduplication from changing the value field to // something not in the archive. @@ -524,20 +808,35 @@ void HeapShared::copy_interned_strings() { delete_seen_objects_table(); } -void HeapShared::copy_special_objects() { - // Archive special objects that do not belong to any subgraphs +void HeapShared::copy_special_subgraph() { + copy_interned_strings(); + init_seen_objects_table(); - archive_java_mirrors(); - archive_strings(); - Universe::archive_exception_instances(); + { + archive_java_mirrors(); + archive_strings(); + Universe::archive_exception_instances(); + } delete_seen_objects_table(); } +void HeapShared::prepare_resolved_references() { + GrowableArray* klasses = ArchiveBuilder::current()->klasses(); + for (int i = 0; i < klasses->length(); i++) { + Klass* src_k = klasses->at(i); + if (src_k->is_instance_klass()) { + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(InstanceKlass::cast(src_k)); + buffered_ik->constants()->prepare_resolved_references_for_archiving(); + } + } +} + void HeapShared::copy_objects() { assert(HeapShared::can_write(), "must be"); - copy_interned_strings(); - copy_special_objects(); + prepare_resolved_references(); + find_all_aot_initialized_classes(); + copy_special_subgraph(); archive_object_subgraphs(archive_subgraph_entry_fields, false /* is_full_module_graph */); @@ -549,6 +848,198 @@ void HeapShared::copy_objects() { } } +// Closure used by HeapShared::scan_for_aot_initialized_classes() to look for all objects +// that are reachable from a given root. +class HeapShared::AOTInitializedClassScanner : public BasicOopIterateClosure { + bool _made_progress; + + template void check(T *p) { + oop obj = HeapAccess<>::oop_load(p); + if (!java_lang_Class::is_instance(obj)) { + // Don't scan the mirrors, as we may see an orig_mirror while scanning + // the object graph, .... TODO more info + _made_progress |= HeapShared::scan_for_aot_initialized_classes(obj); + } + } + +public: + AOTInitializedClassScanner() : _made_progress(false) {} + void do_oop(narrowOop *p) { check(p); } + void do_oop( oop *p) { check(p); } + bool made_progress() { return _made_progress; } +}; + +// If has been initialized during the assembly phase, mark its +// has_aot_initialized_mirror bit. And then do the same for all supertypes of +// . +// +// Note: a super interface of may not have been initialized, if +// has not declared any default methods. +// +// Note: this function doesn not call InstanceKlass::initialize() -- we are inside +// a safepoint. +// +// Returns true if one or more classes have been newly marked. +static bool mark_for_aot_initialization(InstanceKlass* buffered_ik) { + assert(SafepointSynchronize::is_at_safepoint(), "sanity"); + assert(ArchiveBuilder::current()->is_in_buffer_space(buffered_ik), "sanity"); + + if (buffered_ik->has_aot_initialized_mirror()) { // already marked + return false; + } + + bool made_progress = false; + if (buffered_ik->is_initialized()) { + if (log_is_enabled(Info, cds, init)) { + ResourceMark rm; + log_info(cds, init)("Mark class for aot-init: %s", buffered_ik->external_name()); + } + + InstanceKlass* src_ik = ArchiveBuilder::current()->get_source_addr(buffered_ik); + + // If we get here with a "wild" user class, which may have + // uncontrolled code, exit with an error. Obviously + // filtering logic upstream needs to detect APP classes and not mark + // them for aot-init in the first place, but this will be the final + // firewall. + +#ifndef PRODUCT + // ArchiveHeapTestClass is used for a very small number of internal regression + // tests (non-product builds only). It may initialize some unexpected classes. + if (ArchiveHeapTestClass == nullptr) +#endif + { + if (!src_ik->in_javabase_module()) { + // Class/interface types in the boot loader may have been initialized as side effects + // of JVM bootstrap code, so they are fine. But we need to check all other classes. + if (buffered_ik->is_interface()) { + // This probably means a bug in AOTConstantPoolResolver.::is_indy_resolution_deterministic() + guarantee(!buffered_ik->interface_needs_clinit_execution_as_super(), + "should not have initialized an interface whose might have unpredictable side effects"); + } else { + // "normal" classes + guarantee(HeapShared::is_archivable_hidden_klass(buffered_ik), + "should not have initialized any non-interface, non-hidden classes outside of java.base"); + } + } + } + + buffered_ik->set_has_aot_initialized_mirror(); + if (AOTClassInitializer::is_runtime_setup_required(src_ik)) { + buffered_ik->set_is_runtime_setup_required(); + } + made_progress = true; + + InstanceKlass* super = buffered_ik->java_super(); + if (super != nullptr) { + mark_for_aot_initialization(super); + } + + Array* interfaces = buffered_ik->transitive_interfaces(); + for (int i = 0; i < interfaces->length(); i++) { + InstanceKlass* intf = interfaces->at(i); + mark_for_aot_initialization(intf); + if (!intf->is_initialized()) { + assert(!intf->interface_needs_clinit_execution_as_super(/*also_check_supers*/false), "sanity"); + assert(!intf->has_aot_initialized_mirror(), "must not be marked"); + } + } + } + + return made_progress; +} + +void HeapShared::find_all_aot_initialized_classes() { + if (!CDSConfig::is_dumping_aot_linked_classes()) { + return; + } + + init_seen_objects_table(); + find_all_aot_initialized_classes_helper(); + delete_seen_objects_table(); +} + +// Recursively find all class that should be aot-initialized: +// - the class has at least one instance that can be reachable from the special subgraph; or +// - the class is hard-coded in AOTClassInitializer::can_archive_initialized_mirror() +void HeapShared::find_all_aot_initialized_classes_helper() { + GrowableArray* klasses = ArchiveBuilder::current()->klasses(); + assert(klasses != nullptr, "sanity"); + + // First scan all resolved constant pools references. + for (int i = 0; i < klasses->length(); i++) { + Klass* src_k = klasses->at(i); + if (src_k->is_instance_klass()) { + InstanceKlass* src_ik = InstanceKlass::cast(src_k); + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(src_ik); + objArrayOop rr = get_archived_resolved_references(src_ik); + if (rr != nullptr) { + objArrayOop scratch_rr = scratch_resolved_references(src_ik->constants()); + for (int i = 0; i < scratch_rr->length(); i++) { + scan_for_aot_initialized_classes(scratch_rr->obj_at(i)); + } + } + + // If a class is hard-coded to be aot-initialize, mark it as such. + if (AOTClassInitializer::can_archive_initialized_mirror(src_ik)) { + mark_for_aot_initialization(buffered_ik); + } + } + } + + // These objects also belong to the special subgraph + scan_for_aot_initialized_classes(Universe::null_ptr_exception_instance()); + scan_for_aot_initialized_classes(Universe::arithmetic_exception_instance()); + scan_for_aot_initialized_classes(Universe::internal_error_instance()); + scan_for_aot_initialized_classes(Universe::array_index_out_of_bounds_exception_instance()); + scan_for_aot_initialized_classes(Universe::array_store_exception_instance()); + scan_for_aot_initialized_classes(Universe::class_cast_exception_instance()); + + bool made_progress; + do { + // In each pass, we copy the scratch mirrors of the classes that were marked + // as aot-init in the previous pass. We then scan these mirrors, which may + // mark more classes. Keep iterating until no more progress can be made. + made_progress = false; + for (int i = 0; i < klasses->length(); i++) { + Klass* orig_k = klasses->at(i); + if (orig_k->is_instance_klass()) { + InstanceKlass* orig_ik = InstanceKlass::cast(orig_k); + if (ArchiveBuilder::current()->get_buffered_addr(orig_ik)->has_aot_initialized_mirror()) { + oop orig_mirror = orig_ik->java_mirror(); + oop scratch_mirror = scratch_java_mirror(orig_k); + if (!has_been_seen_during_subgraph_recording(scratch_mirror)) { + // Scan scratch_mirror instead of orig_mirror (which has fields like ClassLoader that + // are not archived). + copy_aot_initialized_mirror(orig_k, orig_mirror, scratch_mirror); + made_progress |= scan_for_aot_initialized_classes(scratch_mirror); + } + } + } + } + } while (made_progress); +} + +bool HeapShared::scan_for_aot_initialized_classes(oop obj) { + if (obj == nullptr || has_been_seen_during_subgraph_recording(obj)) { + return false; + } + set_has_been_seen_during_subgraph_recording(obj); + + bool made_progress = false; + Klass* k = obj->klass(); + if (k->is_instance_klass()) { + InstanceKlass* orig_ik = InstanceKlass::cast(k); + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(orig_ik); + made_progress = mark_for_aot_initialization(buffered_ik); + } + + AOTInitializedClassScanner scanner; + obj->oop_iterate(&scanner); + made_progress |= scanner.made_progress(); + return made_progress; +} + // // Subgraph archiving support // @@ -607,8 +1098,14 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) { } if (buffered_k->is_instance_klass()) { - assert(InstanceKlass::cast(buffered_k)->is_shared_boot_class(), - "must be boot class"); + if (CDSConfig::is_dumping_invokedynamic()) { + assert(InstanceKlass::cast(buffered_k)->is_shared_boot_class() || + HeapShared::is_lambda_proxy_klass(InstanceKlass::cast(buffered_k)), + "we can archive only instances of boot classes or lambda proxy classes"); + } else { + assert(InstanceKlass::cast(buffered_k)->is_shared_boot_class(), + "must be boot class"); + } // vmClasses::xxx_klass() are not updated, need to check // the original Klass* if (orig_k == vmClasses::String_klass() || @@ -617,6 +1114,10 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) { // to the sub-graph object class list. return; } + if (buffered_k->has_aot_initialized_mirror()) { + // No need to add to the runtime-init list. + return; + } check_allowed_klass(InstanceKlass::cast(orig_k)); } else if (buffered_k->is_objArray_klass()) { Klass* abk = ObjArrayKlass::cast(buffered_k)->bottom_klass(); @@ -653,19 +1154,30 @@ void KlassSubGraphInfo::check_allowed_klass(InstanceKlass* ik) { return; } + const char* lambda_msg = ""; + if (CDSConfig::is_dumping_invokedynamic()) { + lambda_msg = ", or a lambda proxy class"; + if (HeapShared::is_lambda_proxy_klass(ik) && + (ik->class_loader() == nullptr || + ik->class_loader() == SystemDictionary::java_platform_loader() || + ik->class_loader() == SystemDictionary::java_system_loader())) { + return; + } + } + #ifndef PRODUCT - if (!ik->module()->is_named() && ik->package() == nullptr) { + if (!ik->module()->is_named() && ik->package() == nullptr && ArchiveHeapTestClass != nullptr) { // This class is loaded by ArchiveHeapTestClass return; } - const char* extra_msg = ", or in an unnamed package of an unnamed module"; + const char* testcls_msg = ", or a test class in an unnamed package of an unnamed module"; #else - const char* extra_msg = ""; + const char* testcls_msg = ""; #endif ResourceMark rm; - log_error(cds, heap)("Class %s not allowed in archive heap. Must be in java.base%s", - ik->external_name(), extra_msg); + log_error(cds, heap)("Class %s not allowed in archive heap. Must be in java.base%s%s", + ik->external_name(), lambda_msg, testcls_msg); MetaspaceShared::unrecoverable_writing_error(); } @@ -727,13 +1239,18 @@ void ArchivedKlassSubGraphInfoRecord::init(KlassSubGraphInfo* info) { int num_subgraphs_klasses = subgraph_object_klasses->length(); _subgraph_object_klasses = ArchiveBuilder::new_ro_array(num_subgraphs_klasses); + bool is_special = (_k == ArchiveBuilder::get_buffered_klass(vmClasses::Object_klass())); for (int i = 0; i < num_subgraphs_klasses; i++) { Klass* subgraph_k = subgraph_object_klasses->at(i); if (log_is_enabled(Info, cds, heap)) { ResourceMark rm; + const char* owner_name = is_special ? "" : _k->external_name(); + if (subgraph_k->is_instance_klass()) { + InstanceKlass* src_ik = InstanceKlass::cast(ArchiveBuilder::current()->get_source_addr(subgraph_k)); + } log_info(cds, heap)( "Archived object klass %s (%2d) => %s", - _k->external_name(), i, subgraph_k->external_name()); + owner_name, i, subgraph_k->external_name()); } _subgraph_object_klasses->at_put(i, subgraph_k); ArchivePtrMarker::mark_pointer(_subgraph_object_klasses->adr_at(i)); @@ -745,16 +1262,14 @@ void ArchivedKlassSubGraphInfoRecord::init(KlassSubGraphInfo* info) { ArchivePtrMarker::mark_pointer(&_subgraph_object_klasses); } -struct CopyKlassSubGraphInfoToArchive : StackObj { +class HeapShared::CopyKlassSubGraphInfoToArchive : StackObj { CompactHashtableWriter* _writer; +public: CopyKlassSubGraphInfoToArchive(CompactHashtableWriter* writer) : _writer(writer) {} bool do_entry(Klass* klass, KlassSubGraphInfo& info) { if (info.subgraph_object_klasses() != nullptr || info.subgraph_entry_fields() != nullptr) { - ArchivedKlassSubGraphInfoRecord* record = - (ArchivedKlassSubGraphInfoRecord*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedKlassSubGraphInfoRecord)); - record->init(&info); - + ArchivedKlassSubGraphInfoRecord* record = HeapShared::archive_subgraph_info(&info); Klass* buffered_k = ArchiveBuilder::get_buffered_klass(klass); unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary((address)buffered_k); u4 delta = ArchiveBuilder::current()->any_to_offset_u4(record); @@ -764,6 +1279,16 @@ struct CopyKlassSubGraphInfoToArchive : StackObj { } }; +ArchivedKlassSubGraphInfoRecord* HeapShared::archive_subgraph_info(KlassSubGraphInfo* info) { + ArchivedKlassSubGraphInfoRecord* record = + (ArchivedKlassSubGraphInfoRecord*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedKlassSubGraphInfoRecord)); + record->init(info); + if (info == _dump_time_special_subgraph) { + _run_time_special_subgraph = record; + } + return record; +} + // Build the records of archived subgraph infos, which include: // - Entry points to all subgraphs from the containing class mirror. The entry // points are static fields in the mirror. For each entry point, the field @@ -820,6 +1345,7 @@ void HeapShared::serialize_tables(SerializeClosure* soc) { #endif _run_time_subgraph_info_table.serialize_header(soc); + soc->do_ptr(&_run_time_special_subgraph); } static void verify_the_heap(Klass* k, const char* which) { @@ -887,6 +1413,65 @@ void HeapShared::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) } } +void HeapShared::initialize_java_lang_invoke(TRAPS) { + if (CDSConfig::is_loading_invokedynamic() || CDSConfig::is_dumping_invokedynamic()) { + resolve_or_init("java/lang/invoke/Invokers$Holder", true, CHECK); + resolve_or_init("java/lang/invoke/MethodHandle", true, CHECK); + resolve_or_init("java/lang/invoke/MethodHandleNatives", true, CHECK); + resolve_or_init("java/lang/invoke/DirectMethodHandle$Holder", true, CHECK); + resolve_or_init("java/lang/invoke/DelegatingMethodHandle$Holder", true, CHECK); + resolve_or_init("java/lang/invoke/LambdaForm$Holder", true, CHECK); + resolve_or_init("java/lang/invoke/BoundMethodHandle$Species_L", true, CHECK); + } +} + +// Initialize the InstanceKlasses of objects that are reachable from the following roots: +// - interned strings +// - Klass::java_mirror() -- including aot-initialized mirrors such as those of Enum klasses. +// - ConstantPool::resolved_references() +// - Universe::_exception_instance() +// +// For example, if this enum class is initialized at AOT cache assembly time: +// +// enum Fruit { +// APPLE, ORANGE, BANANA; +// static final Set HAVE_SEEDS = new HashSet<>(Arrays.asList(APPLE, ORANGE)); +// } +// +// the aot-initialized mirror of Fruit has a static field that references HashSet, which +// should be initialized before any Java code can access the Fruit class. Note that +// HashSet itself doesn't necessary need to be an aot-initialized class. +void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { + if (!ArchiveHeapLoader::is_in_use()) { + return; + } + + assert( _run_time_special_subgraph != nullptr, "must be"); + Array* klasses = _run_time_special_subgraph->subgraph_object_klasses(); + if (klasses != nullptr) { + for (int pass = 0; pass < 2; pass ++) { + for (int i = 0; i < klasses->length(); i++) { + Klass* k = klasses->at(i); + if (k->class_loader_data() == nullptr) { + // This class is not yet loaded. We will initialize it in a later phase. + // For example, we have loaded only AOTLinkedClassCategory::BOOT1 classes + // but k is part of AOTLinkedClassCategory::BOOT2. + continue; + } + if (k->class_loader() == class_loader()) { + if (pass == 0) { + if (k->is_instance_klass()) { + InstanceKlass::cast(k)->link_class(CHECK); + } + } else { + resolve_or_init(k, /*do_init*/true, CHECK); + } + } + } + } + } +} + void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k) { JavaThread* THREAD = current; if (!ArchiveHeapLoader::is_in_use()) { @@ -990,6 +1575,19 @@ HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAP return record; } +void HeapShared::resolve_or_init(const char* klass_name, bool do_init, TRAPS) { + TempNewSymbol klass_name_sym = SymbolTable::new_symbol(klass_name); + InstanceKlass* k = SystemDictionaryShared::find_builtin_class(klass_name_sym); + if (k == nullptr) { + return; + } + assert(k->is_shared_boot_class(), "sanity"); + resolve_or_init(k, false, CHECK); + if (do_init) { + resolve_or_init(k, true, CHECK); + } +} + void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { if (!do_init) { if (k->class_loader_data() == nullptr) { @@ -1022,7 +1620,11 @@ void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphI int field_offset = entry_field_records->at(i); int root_index = entry_field_records->at(i+1); oop v = get_root(root_index, /*clear=*/true); - m->obj_field_put(field_offset, v); + if (k->has_aot_initialized_mirror()) { + assert(v == m->obj_field(field_offset), "must be aot-initialized"); + } else { + m->obj_field_put(field_offset, v); + } log_debug(cds, heap)(" " PTR_FORMAT " init field @ %2d = " PTR_FORMAT, p2i(k), field_offset, p2i(v)); } @@ -1030,8 +1632,9 @@ void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphI // mirror after this point. if (log_is_enabled(Info, cds, heap)) { ResourceMark rm; - log_info(cds, heap)("initialize_from_archived_subgraph %s " PTR_FORMAT "%s", - k->external_name(), p2i(k), JvmtiExport::is_early_phase() ? " (early)" : ""); + log_info(cds, heap)("initialize_from_archived_subgraph %s " PTR_FORMAT "%s%s", + k->external_name(), p2i(k), JvmtiExport::is_early_phase() ? " (early)" : "", + k->has_aot_initialized_mirror() ? " (aot-inited)" : ""); } } @@ -1137,6 +1740,20 @@ HeapShared::CachedOopInfo HeapShared::make_cached_oop_info(oop obj) { return CachedOopInfo(referrer, points_to_oops_checker.result()); } +void HeapShared::init_box_classes(TRAPS) { + if (ArchiveHeapLoader::is_in_use()) { + vmClasses::Boolean_klass()->initialize(CHECK); + vmClasses::Character_klass()->initialize(CHECK); + vmClasses::Float_klass()->initialize(CHECK); + vmClasses::Double_klass()->initialize(CHECK); + vmClasses::Byte_klass()->initialize(CHECK); + vmClasses::Short_klass()->initialize(CHECK); + vmClasses::Integer_klass()->initialize(CHECK); + vmClasses::Long_klass()->initialize(CHECK); + vmClasses::Void_klass()->initialize(CHECK); + } +} + // (1) If orig_obj has not been archived yet, archive it. // (2) If orig_obj has not been seen yet (since start_recording_subgraph() was called), // trace all objects that are reachable from it, and make sure these objects are archived. @@ -1151,25 +1768,56 @@ bool HeapShared::archive_reachable_objects_from(int level, // If you get an error here, you probably made a change in the JDK library that has added // these objects that are referenced (directly or indirectly) by static fields. ResourceMark rm; - log_error(cds, heap)("Cannot archive object of class %s", orig_obj->klass()->external_name()); - if (log_is_enabled(Trace, cds, heap)) { - WalkOopAndArchiveClosure* walker = WalkOopAndArchiveClosure::current(); - if (walker != nullptr) { - LogStream ls(Log(cds, heap)::trace()); - CDSHeapVerifier::trace_to_root(&ls, walker->referencing_obj()); - } - } + log_error(cds, heap)("Cannot archive object " PTR_FORMAT " of class %s", p2i(orig_obj), orig_obj->klass()->external_name()); + debug_trace(); MetaspaceShared::unrecoverable_writing_error(); } - // java.lang.Class instances cannot be included in an archived object sub-graph. We only support - // them as Klass::_archived_mirror because they need to be specially restored at run time. - // - // If you get an error here, you probably made a change in the JDK library that has added a Class - // object that is referenced (directly or indirectly) by static fields. - if (java_lang_Class::is_instance(orig_obj) && subgraph_info != _default_subgraph_info) { - log_error(cds, heap)("(%d) Unknown java.lang.Class object is in the archived sub-graph", level); - MetaspaceShared::unrecoverable_writing_error(); + if (log_is_enabled(Debug, cds, heap) && java_lang_Class::is_instance(orig_obj)) { + ResourceMark rm; + LogTarget(Debug, cds, heap) log; + LogStream out(log); + out.print("Found java mirror " PTR_FORMAT " ", p2i(orig_obj)); + Klass* k = java_lang_Class::as_Klass(orig_obj); + if (k != nullptr) { + out.print("%s", k->external_name()); + } else { + out.print("primitive"); + } + out.print_cr("; scratch mirror = " PTR_FORMAT, + p2i(scratch_java_mirror(orig_obj))); + } + + if (CDSConfig::is_initing_classes_at_dump_time()) { + 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 + // 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() + || java_lang_Class::is_primitive(orig_obj) + || orig_obj == vmClasses::Boolean_klass()->java_mirror() + || orig_obj == vmClasses::Character_klass()->java_mirror() + || orig_obj == vmClasses::Float_klass()->java_mirror() + || orig_obj == vmClasses::Double_klass()->java_mirror() + || orig_obj == vmClasses::Byte_klass()->java_mirror() + || orig_obj == vmClasses::Short_klass()->java_mirror() + || orig_obj == vmClasses::Integer_klass()->java_mirror() + || orig_obj == vmClasses::Long_klass()->java_mirror() + || orig_obj == vmClasses::Void_klass()->java_mirror()) { + orig_obj = scratch_java_mirror(orig_obj); + assert(orig_obj != nullptr, "must be archived"); + } else { + // If you get an error here, you probably made a change in the JDK library that has added a Class + // object that is referenced (directly or indirectly) by an ArchivableStaticFieldInfo + // defined at the top of this file. + log_error(cds, heap)("(%d) Unknown java.lang.Class object is in the archived sub-graph", level); + debug_trace(); + MetaspaceShared::unrecoverable_writing_error(); + } } if (has_been_seen_during_subgraph_recording(orig_obj)) { @@ -1209,9 +1857,15 @@ bool HeapShared::archive_reachable_objects_from(int level, WalkOopAndArchiveClosure walker(level, record_klasses_only, subgraph_info, orig_obj); orig_obj->oop_iterate(&walker); - if (CDSEnumKlass::is_enum_obj(orig_obj)) { - CDSEnumKlass::handle_enum_obj(level + 1, subgraph_info, orig_obj); + if (CDSConfig::is_initing_classes_at_dump_time()) { + // The enum klasses are archived with aot-initialized mirror. + // See AOTClassInitializer::can_archive_initialized_mirror(). + } else { + if (CDSEnumKlass::is_enum_obj(orig_obj)) { + CDSEnumKlass::handle_enum_obj(level + 1, subgraph_info, orig_obj); + } } + return true; } @@ -1329,6 +1983,10 @@ void HeapShared::verify_subgraph_from(oop orig_obj) { void HeapShared::verify_reachable_objects_from(oop obj) { _num_total_verifications ++; + if (java_lang_Class::is_instance(obj)) { + obj = scratch_java_mirror(obj); + assert(obj != nullptr, "must be"); + } if (!has_been_seen_during_subgraph_recording(obj)) { set_has_been_seen_during_subgraph_recording(obj); assert(has_been_archived(obj), "must be"); @@ -1338,35 +1996,35 @@ void HeapShared::verify_reachable_objects_from(oop obj) { } #endif -// The "default subgraph" contains special objects (see heapShared.hpp) that -// can be accessed before we load any Java classes (including java/lang/Class). -// Make sure that these are only instances of the very few specific types -// that we can handle. -void HeapShared::check_default_subgraph_classes() { - GrowableArray* klasses = _default_subgraph_info->subgraph_object_klasses(); - int num = klasses->length(); - for (int i = 0; i < num; i++) { - Klass* subgraph_k = klasses->at(i); - if (log_is_enabled(Info, cds, heap)) { - ResourceMark rm; - log_info(cds, heap)( - "Archived object klass (default subgraph %d) => %s", - i, subgraph_k->external_name()); +void HeapShared::check_special_subgraph_classes() { + if (CDSConfig::is_initing_classes_at_dump_time()) { + // 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". + // + // TODO: we need an automatic tool that checks the safety of aot-initialized + // classes (when we extend the set of aot-initialized classes beyond JEP 483) + return; + } else { + // In this case, the special subgraph should contain a few specific types + GrowableArray* klasses = _dump_time_special_subgraph->subgraph_object_klasses(); + int num = klasses->length(); + for (int i = 0; i < num; i++) { + Klass* subgraph_k = klasses->at(i); + Symbol* name = ArchiveBuilder::current()->get_source_addr(subgraph_k->name()); + if (subgraph_k->is_instance_klass() && + name != vmSymbols::java_lang_Class() && + name != vmSymbols::java_lang_String() && + name != vmSymbols::java_lang_ArithmeticException() && + name != vmSymbols::java_lang_ArrayIndexOutOfBoundsException() && + name != vmSymbols::java_lang_ArrayStoreException() && + name != vmSymbols::java_lang_ClassCastException() && + name != vmSymbols::java_lang_InternalError() && + name != vmSymbols::java_lang_NullPointerException()) { + ResourceMark rm; + fatal("special subgraph cannot have objects of type %s", subgraph_k->external_name()); + } } - - Symbol* name = ArchiveBuilder::current()->get_source_addr(subgraph_k->name()); - guarantee(name == vmSymbols::java_lang_Class() || - name == vmSymbols::java_lang_String() || - name == vmSymbols::java_lang_ArithmeticException() || - name == vmSymbols::java_lang_NullPointerException() || - name == vmSymbols::java_lang_InternalError() || - name == vmSymbols::java_lang_ArrayIndexOutOfBoundsException() || - name == vmSymbols::java_lang_ArrayStoreException() || - name == vmSymbols::java_lang_ClassCastException() || - name == vmSymbols::object_array_signature() || - name == vmSymbols::byte_array_signature() || - name == vmSymbols::char_array_signature(), - "default subgraph can have only these objects"); } } @@ -1572,8 +2230,8 @@ bool HeapShared::is_a_test_class_in_unnamed_module(Klass* ik) { return false; } - // See KlassSubGraphInfo::check_allowed_klass() - only two types of - // classes are allowed: + // See KlassSubGraphInfo::check_allowed_klass() - we only allow test classes + // to be: // (A) java.base classes (which must not be in the unnamed module) // (B) test classes which must be in the unnamed package of the unnamed module. // So if we see a '/' character in the class name, it must be in (A); @@ -1589,6 +2247,25 @@ bool HeapShared::is_a_test_class_in_unnamed_module(Klass* ik) { return false; } + +void HeapShared::initialize_test_class_from_archive(JavaThread* current) { + Klass* k = _test_class; + if (k != nullptr && ArchiveHeapLoader::is_in_use()) { + JavaThread* THREAD = current; + ExceptionMark em(THREAD); + const ArchivedKlassSubGraphInfoRecord* record = + resolve_or_init_classes_for_subgraph_of(k, /*do_init=*/false, THREAD); + + // The _test_class is in the unnamed module, so it can't call CDS.initializeFromArchive() + // from its method. So we set up its "archivedObjects" field first, before + // calling its . This is not strictly clean, but it's a convenient way to write unit + // test cases (see test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java). + if (record != nullptr) { + init_archived_fields_for(k, record); + } + resolve_or_init_classes_for_subgraph_of(k, /*do_init=*/true, THREAD); + } +} #endif void HeapShared::init_for_dumping(TRAPS) { @@ -1664,6 +2341,15 @@ void HeapShared::add_to_dumped_interned_strings(oop string) { } } +void HeapShared::debug_trace() { + ResourceMark rm; + WalkOopAndArchiveClosure* walker = WalkOopAndArchiveClosure::current(); + if (walker != nullptr) { + LogStream ls(Log(cds, heap)::error()); + CDSHeapVerifier::trace_to_root(&ls, walker->referencing_obj()); + } +} + #ifndef PRODUCT // At dump-time, find the location of all the non-null oop pointers in an archived heap // region. This way we can quickly relocate all the pointers without using diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index 317b791f5d3..618adeb308f 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -166,10 +166,16 @@ public: static bool is_subgraph_root_class(InstanceKlass* ik); // Scratch objects for archiving Klass::java_mirror() - static oop scratch_java_mirror(BasicType t) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); - static oop scratch_java_mirror(Klass* k) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + static oop scratch_java_mirror(BasicType t) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + static oop scratch_java_mirror(Klass* k) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + static oop scratch_java_mirror(oop java_mirror) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); static bool is_archived_boot_layer_available(JavaThread* current) NOT_CDS_JAVA_HEAP_RETURN_(false); + // Look for all hidden classes that are referenced by archived objects. + static void start_finding_required_hidden_classes() NOT_CDS_JAVA_HEAP_RETURN; + static void find_required_hidden_classes_in_object(oop o) NOT_CDS_JAVA_HEAP_RETURN; + static void end_finding_required_hidden_classes() NOT_CDS_JAVA_HEAP_RETURN; + private: #if INCLUDE_CDS_JAVA_HEAP static bool _disable_writing; @@ -184,12 +190,15 @@ private: static void count_allocation(size_t size); static void print_stats(); + static void debug_trace(); public: static unsigned oop_hash(oop const& p); static unsigned string_oop_hash(oop const& string) { return java_lang_String::hash_code(string); } + class CopyKlassSubGraphInfoToArchive; + class CachedOopInfo { // Used by CDSHeapVerifier. oop _orig_referrer; @@ -251,7 +260,11 @@ private: static DumpTimeKlassSubGraphInfoTable* _dump_time_subgraph_info_table; static RunTimeKlassSubGraphInfoTable _run_time_subgraph_info_table; + class FindRequiredHiddenClassesOopClosure; + static void find_required_hidden_classes_helper(ArchivableStaticFieldInfo fields[]); + static CachedOopInfo make_cached_oop_info(oop obj); + static ArchivedKlassSubGraphInfoRecord* archive_subgraph_info(KlassSubGraphInfo* info); static void archive_object_subgraphs(ArchivableStaticFieldInfo fields[], bool is_full_module_graph); @@ -265,7 +278,7 @@ private: InstanceKlass* k, int field_offset) PRODUCT_RETURN; static void verify_reachable_objects_from(oop obj) PRODUCT_RETURN; static void verify_subgraph_from(oop orig_obj) PRODUCT_RETURN; - static void check_default_subgraph_classes(); + static void check_special_subgraph_classes(); static KlassSubGraphInfo* init_subgraph_info(Klass *k, bool is_full_module_graph); static KlassSubGraphInfo* get_subgraph_info(Klass *k); @@ -287,12 +300,14 @@ private: static SeenObjectsTable *_seen_objects_table; - // The "default subgraph" is the root of all archived objects that do not belong to any - // of the classes defined in the _archive_subgraph_entry_fields[] arrays: + // The "special subgraph" contains all the archived objects that are reachable + // from the following roots: // - interned strings - // - Klass::java_mirror() + // - Klass::java_mirror() -- including aot-initialized mirrors such as those of Enum klasses. // - ConstantPool::resolved_references() - static KlassSubGraphInfo* _default_subgraph_info; + // - Universe::_exception_instance() + static KlassSubGraphInfo* _dump_time_special_subgraph; // for collecting info during dump time + static ArchivedKlassSubGraphInfoRecord* _run_time_special_subgraph; // for initializing classes during run time. static GrowableArrayCHeap* _pending_roots; static GrowableArrayCHeap* _root_segments; @@ -330,7 +345,7 @@ private: static bool has_been_seen_during_subgraph_recording(oop obj); static void set_has_been_seen_during_subgraph_recording(oop obj); static bool archive_object(oop obj); - + static void copy_aot_initialized_mirror(Klass* orig_k, oop orig_mirror, oop m); static void copy_interned_strings(); static void resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]); @@ -338,6 +353,7 @@ private: static void clear_archived_roots_of(Klass* k); static const ArchivedKlassSubGraphInfoRecord* resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS); + static void resolve_or_init(const char* klass_name, bool do_init, TRAPS); static void resolve_or_init(Klass* k, bool do_init, TRAPS); static void init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphInfoRecord* record); @@ -352,8 +368,16 @@ private: static void fill_failed_loaded_region(); static void mark_native_pointers(oop orig_obj); static bool has_been_archived(oop orig_obj); + static void prepare_resolved_references(); static void archive_java_mirrors(); static void archive_strings(); + static void copy_special_subgraph(); + + class AOTInitializedClassScanner; + static void find_all_aot_initialized_classes(); + static void find_all_aot_initialized_classes_helper(); + static bool scan_for_aot_initialized_classes(oop obj); + public: static void reset_archived_object_states(TRAPS); static void create_archived_object_cache() { @@ -371,7 +395,6 @@ private: static int archive_exception_instance(oop exception); static void archive_objects(ArchiveHeapInfo* heap_info); static void copy_objects(); - static void copy_special_objects(); static bool archive_reachable_objects_from(int level, KlassSubGraphInfo* subgraph_info, @@ -420,6 +443,7 @@ private: static objArrayOop scratch_resolved_references(ConstantPool* src); static void add_scratch_resolved_references(ConstantPool* src, objArrayOop dest) NOT_CDS_JAVA_HEAP_RETURN; static void init_scratch_objects(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + static void init_box_classes(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; static bool is_heap_region(int idx) { CDS_JAVA_HEAP_ONLY(return (idx == MetaspaceShared::hp);) NOT_CDS_JAVA_HEAP_RETURN_(false); @@ -436,7 +460,16 @@ private: #ifndef PRODUCT static bool is_a_test_class_in_unnamed_module(Klass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); + static void initialize_test_class_from_archive(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; #endif + + static void initialize_java_lang_invoke(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + static void init_classes_for_special_subgraph(Handle loader, TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + + static bool is_lambda_form_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_lambda_proxy_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_string_concat_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_archivable_hidden_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); }; #if INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/lambdaFormInvokers.cpp b/src/hotspot/share/cds/lambdaFormInvokers.cpp index 271c6c76d7a..5455a5e66f2 100644 --- a/src/hotspot/share/cds/lambdaFormInvokers.cpp +++ b/src/hotspot/share/cds/lambdaFormInvokers.cpp @@ -27,8 +27,8 @@ #include "cds/lambdaFormInvokers.hpp" #include "cds/metaspaceShared.hpp" #include "cds/regeneratedClasses.hpp" -#include "classfile/classLoadInfo.hpp" #include "classfile/classFileStream.hpp" +#include "classfile/classLoadInfo.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/klassFactory.hpp" #include "classfile/symbolTable.hpp" @@ -96,6 +96,21 @@ void LambdaFormInvokers::regenerate_holder_classes(TRAPS) { return; } + if (CDSConfig::is_dumping_static_archive() && CDSConfig::is_dumping_invokedynamic()) { + // Work around JDK-8310831, as some methods in lambda form holder classes may not get generated. + log_info(cds)("Archived MethodHandles may refer to lambda form holder classes. Cannot regenerate."); + return; + } + + if (CDSConfig::is_dumping_dynamic_archive() && CDSConfig::is_dumping_aot_linked_classes() && + CDSConfig::is_using_aot_linked_classes()) { + // The base archive may have some pre-resolved CP entries that point to the lambda form holder + // classes in the base archive. If we generate new versions of these classes, those CP entries + // will be pointing to invalid classes. + log_info(cds)("Base archive already has aot-linked lambda form holder classes. Cannot regenerate."); + return; + } + ResourceMark rm(THREAD); Symbol* cds_name = vmSymbols::jdk_internal_misc_CDS(); diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index 3cb1374efb1..b2f5f420365 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -23,16 +23,17 @@ */ #include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapLoader.hpp" #include "cds/archiveHeapWriter.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/cdsProtectionDomain.hpp" -#include "cds/cds_globals.hpp" #include "cds/classListParser.hpp" #include "cds/classListWriter.hpp" -#include "cds/classPrelinker.hpp" #include "cds/cppVtables.hpp" #include "cds/dumpAllocStats.hpp" #include "cds/dynamicArchive.hpp" @@ -98,6 +99,7 @@ bool MetaspaceShared::_remapped_readwrite = false; void* MetaspaceShared::_shared_metaspace_static_top = nullptr; intx MetaspaceShared::_relocation_delta; char* MetaspaceShared::_requested_base_address; +Array* MetaspaceShared::_archived_method_handle_intrinsics = nullptr; bool MetaspaceShared::_use_optimized_module_handling = true; // The CDS archive is divided into the following regions: @@ -308,8 +310,12 @@ void MetaspaceShared::post_initialize(TRAPS) { } } +// Extra java.lang.Strings to be added to the archive static GrowableArrayCHeap* _extra_interned_strings = nullptr; +// Extra Symbols to be added to the archive static GrowableArrayCHeap* _extra_symbols = nullptr; +// Methods managed by SystemDictionary::find_method_handle_intrinsic() to be added to the archive +static GrowableArray* _pending_method_handle_intrinsics = NULL; void MetaspaceShared::read_extra_data(JavaThread* current, const char* filename) { _extra_interned_strings = new GrowableArrayCHeap(10000); @@ -360,6 +366,30 @@ void MetaspaceShared::read_extra_data(JavaThread* current, const char* filename) } } +void MetaspaceShared::make_method_handle_intrinsics_shareable() { + for (int i = 0; i < _pending_method_handle_intrinsics->length(); i++) { + Method* m = ArchiveBuilder::current()->get_buffered_addr(_pending_method_handle_intrinsics->at(i)); + m->remove_unshareable_info(); + // Each method has its own constant pool (which is distinct from m->method_holder()->constants()); + m->constants()->remove_unshareable_info(); + } +} + +void MetaspaceShared::write_method_handle_intrinsics() { + int len = _pending_method_handle_intrinsics->length(); + _archived_method_handle_intrinsics = ArchiveBuilder::new_ro_array(len); + int word_size = _archived_method_handle_intrinsics->size(); + for (int i = 0; i < len; i++) { + Method* m = _pending_method_handle_intrinsics->at(i); + ArchiveBuilder::current()->write_pointer_in_buffer(_archived_method_handle_intrinsics->adr_at(i), m); + word_size += m->size() + m->constMethod()->size() + m->constants()->size(); + if (m->constants()->cache() != nullptr) { + word_size += m->constants()->cache()->size(); + } + } + log_info(cds)("Archived %d method handle intrinsics (%d bytes)", len, word_size * BytesPerWord); +} + // About "serialize" -- // // This is (probably a badly named) way to read/write a data stream of pointers and @@ -431,7 +461,7 @@ void MetaspaceShared::serialize(SerializeClosure* soc) { StringTable::serialize_shared_table_header(soc); HeapShared::serialize_tables(soc); SystemDictionaryShared::serialize_dictionary_headers(soc); - + AOTLinkedClassBulkLoader::serialize(soc, true); InstanceMirrorKlass::serialize_offsets(soc); // Dump/restore well known classes (pointers) @@ -439,6 +469,7 @@ void MetaspaceShared::serialize(SerializeClosure* soc) { soc->do_tag(--tag); CDS_JAVA_HEAP_ONLY(ClassLoaderDataShared::serialize(soc);) + soc->do_ptr((void**)&_archived_method_handle_intrinsics); LambdaFormInvokers::serialize(soc); soc->do_tag(666); @@ -526,6 +557,10 @@ public: it->push(_extra_symbols->adr_at(i)); } } + + for (int i = 0; i < _pending_method_handle_intrinsics->length(); i++) { + it->push(_pending_method_handle_intrinsics->adr_at(i)); + } } }; @@ -548,6 +583,8 @@ char* VM_PopulateDumpSharedSpace::dump_read_only_tables() { ArchiveBuilder::OtherROAllocMark mark; SystemDictionaryShared::write_to_archive(); + AOTClassLinker::write_to_archive(); + MetaspaceShared::write_method_handle_intrinsics(); // Write lambform lines into archive LambdaFormInvokers::dump_static_archive_invokers(); @@ -566,13 +603,20 @@ void VM_PopulateDumpSharedSpace::doit() { DEBUG_ONLY(SystemDictionaryShared::NoClassLoadingMark nclm); + _pending_method_handle_intrinsics = new (mtClassShared) GrowableArray(256, mtClassShared); + if (CDSConfig::is_dumping_aot_linked_classes()) { + // When dumping AOT-linked classes, some classes may have direct references to a method handle + // intrinsic. The easiest thing is to save all of them into the AOT cache. + SystemDictionary::get_all_method_handle_intrinsics(_pending_method_handle_intrinsics); + } + FileMapInfo::check_nonempty_dir_in_shared_path_table(); NOT_PRODUCT(SystemDictionary::verify();) // Block concurrent class unloading from changing the _dumptime_table MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); - SystemDictionaryShared::check_excluded_classes(); + SystemDictionaryShared::find_all_archivable_classes(); _builder.gather_source_objs(); _builder.reserve_buffer(); @@ -589,6 +633,7 @@ void VM_PopulateDumpSharedSpace::doit() { log_info(cds)("Make classes shareable"); _builder.make_klasses_shareable(); + MetaspaceShared::make_method_handle_intrinsics_shareable(); char* early_serialized_data = dump_early_read_only_tables(); char* serialized_data = dump_read_only_tables(); @@ -656,12 +701,12 @@ bool MetaspaceShared::link_class_for_cds(InstanceKlass* ik, TRAPS) { // cpcache to be created. Class verification is done according // to -Xverify setting. bool res = MetaspaceShared::try_link_class(THREAD, ik); - ClassPrelinker::dumptime_resolve_constants(ik, CHECK_(false)); + AOTConstantPoolResolver::dumptime_resolve_constants(ik, CHECK_(false)); return res; } void MetaspaceShared::link_shared_classes(bool jcmd_request, TRAPS) { - ClassPrelinker::initialize(); + AOTClassLinker::initialize(); if (!jcmd_request) { LambdaFormInvokers::regenerate_holder_classes(CHECK); @@ -709,6 +754,7 @@ void MetaspaceShared::prepare_for_dumping() { // Preload classes from a list, populate the shared spaces and dump to a // file. void MetaspaceShared::preload_and_dump(TRAPS) { + CDSConfig::DumperThreadMark dumper_thread_mark(THREAD); ResourceMark rm(THREAD); StaticArchiveBuilder builder; preload_and_dump_impl(builder, THREAD); @@ -723,6 +769,15 @@ void MetaspaceShared::preload_and_dump(TRAPS) { MetaspaceShared::writing_error("Unexpected exception, use -Xlog:cds,exceptions=trace for detail"); } } + + if (!CDSConfig::old_cds_flags_used()) { + // The JLI launcher only recognizes the "old" -Xshare:dump flag. + // When the new -XX:AOTMode=create flag is used, we can't return + // to the JLI launcher, as the launcher will fail when trying to + // run the main class, which is not what we want. + tty->print_cr("AOTCache creation is complete: %s", AOTCache); + vm_exit(0); + } } #if INCLUDE_CDS_JAVA_HEAP && defined(_LP64) @@ -851,6 +906,29 @@ void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS HeapShared::reset_archived_object_states(CHECK); } + if (CDSConfig::is_dumping_invokedynamic()) { + // This assert means that the MethodType and MethodTypeForm tables won't be + // updated concurrently when we are saving their contents into a side table. + assert(CDSConfig::allow_only_single_java_thread(), "Required"); + + JavaValue result(T_VOID); + JavaCalls::call_static(&result, vmClasses::MethodType_klass(), + vmSymbols::createArchivedObjects(), + vmSymbols::void_method_signature(), + CHECK); + + // java.lang.Class::reflectionFactory cannot be archived yet. We set this field + // to null, and it will be initialized again at runtime. + log_debug(cds)("Resetting Class::reflectionFactory"); + TempNewSymbol method_name = SymbolTable::new_symbol("resetArchivedStates"); + Symbol* method_sig = vmSymbols::void_method_signature(); + JavaCalls::call_static(&result, vmClasses::Class_klass(), + method_name, method_sig, CHECK); + + // Perhaps there is a way to avoid hard-coding these names here. + // See discussion in JDK-8342481. + } + // Do this at the very end, when no Java code will be executed. Otherwise // some new strings may be added to the intern table. StringTable::allocate_shared_strings_array(CHECK); @@ -1536,6 +1614,11 @@ MapArchiveResult MetaspaceShared::map_archive(FileMapInfo* mapinfo, char* mapped early_serialize(&rc); } + if (!mapinfo->validate_aot_class_linking()) { + unmap_archive(mapinfo); + return MAP_ARCHIVE_OTHER_FAILURE; + } + mapinfo->set_is_mapped(true); return MAP_ARCHIVE_SUCCESS; } @@ -1596,6 +1679,18 @@ void MetaspaceShared::initialize_shared_spaces() { dynamic_mapinfo->unmap_region(MetaspaceShared::bm); } + LogStreamHandle(Info, cds) lsh; + if (lsh.is_enabled()) { + lsh.print("Using AOT-linked classes: %s (static archive: %s aot-linked classes", + BOOL_TO_STR(CDSConfig::is_using_aot_linked_classes()), + static_mapinfo->header()->has_aot_linked_classes() ? "has" : "no"); + if (dynamic_mapinfo != nullptr) { + lsh.print(", dynamic archive: %s aot-linked classes", + dynamic_mapinfo->header()->has_aot_linked_classes() ? "has" : "no"); + } + lsh.print_cr(")"); + } + // Set up LambdaFormInvokers::_lambdaform_lines for dynamic dump if (CDSConfig::is_dumping_dynamic_archive()) { // Read stored LF format lines stored in static archive diff --git a/src/hotspot/share/cds/metaspaceShared.hpp b/src/hotspot/share/cds/metaspaceShared.hpp index 0b4f6f2ecd4..aaa649d3c0f 100644 --- a/src/hotspot/share/cds/metaspaceShared.hpp +++ b/src/hotspot/share/cds/metaspaceShared.hpp @@ -34,10 +34,12 @@ class ArchiveBuilder; class ArchiveHeapInfo; class FileMapInfo; +class Method; class outputStream; class SerializeClosure; class StaticArchiveBuilder; +template class Array; template class GrowableArray; enum MapArchiveResult { @@ -56,6 +58,7 @@ class MetaspaceShared : AllStatic { static intx _relocation_delta; static char* _requested_base_address; static bool _use_optimized_module_handling; + static Array* _archived_method_handle_intrinsics; public: enum { @@ -111,6 +114,9 @@ public: static void unrecoverable_writing_error(const char* message = nullptr); static void writing_error(const char* message = nullptr); + static void make_method_handle_intrinsics_shareable() NOT_CDS_RETURN; + static void write_method_handle_intrinsics() NOT_CDS_RETURN; + static Array* archived_method_handle_intrinsics() { return _archived_method_handle_intrinsics; } static void early_serialize(SerializeClosure* sc) NOT_CDS_RETURN; static void serialize(SerializeClosure* sc) NOT_CDS_RETURN; diff --git a/src/hotspot/share/cds/runTimeClassInfo.cpp b/src/hotspot/share/cds/runTimeClassInfo.cpp index e1329d9d2f9..5ad9c14d13e 100644 --- a/src/hotspot/share/cds/runTimeClassInfo.cpp +++ b/src/hotspot/share/cds/runTimeClassInfo.cpp @@ -62,7 +62,7 @@ void RunTimeClassInfo::init(DumpTimeClassInfo& info) { } } - if (k->is_hidden()) { + if (k->is_hidden() && info.nest_host() != nullptr) { _nest_host_offset = builder->any_to_offset_u4(info.nest_host()); } if (k->has_archived_enum_objs()) { diff --git a/src/hotspot/share/cds/runTimeClassInfo.hpp b/src/hotspot/share/cds/runTimeClassInfo.hpp index d0f02d1c095..e3c1ad4f8fe 100644 --- a/src/hotspot/share/cds/runTimeClassInfo.hpp +++ b/src/hotspot/share/cds/runTimeClassInfo.hpp @@ -177,7 +177,11 @@ public: InstanceKlass* nest_host() { assert(!ArchiveBuilder::is_active(), "not called when dumping archive"); - return ArchiveUtils::from_offset(_nest_host_offset); + if (_nest_host_offset == 0) { + return nullptr; + } else { + return ArchiveUtils::from_offset(_nest_host_offset); + } } RTLoaderConstraint* loader_constraints() { diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 9cf88e7cf55..1b34f2ebede 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -26,16 +26,17 @@ #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" +#include "cds/heapShared.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.inline.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderExt.hpp" #include "classfile/classLoadInfo.hpp" #include "classfile/javaClasses.hpp" +#include "classfile/klassFactory.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/modules.hpp" #include "classfile/packageEntry.hpp" -#include "classfile/klassFactory.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -1273,7 +1274,7 @@ void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik, assert(stream != nullptr, "sanity"); if (ik->is_hidden()) { - // We do not archive hidden classes. + record_hidden_class(ik); return; } @@ -1375,6 +1376,44 @@ void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik, assert(file_name != nullptr, "invariant"); ClassLoaderExt::record_result(checked_cast(classpath_index), ik, redefined); } + +void ClassLoader::record_hidden_class(InstanceKlass* ik) { + assert(ik->is_hidden(), "must be"); + + s2 classloader_type; + if (HeapShared::is_lambda_form_klass(ik)) { + classloader_type = ClassLoader::BOOT_LOADER; + } else { + oop loader = ik->class_loader(); + + if (loader == nullptr) { + classloader_type = ClassLoader::BOOT_LOADER; + } else if (SystemDictionary::is_platform_class_loader(loader)) { + classloader_type = ClassLoader::PLATFORM_LOADER; + } else if (SystemDictionary::is_system_class_loader(loader)) { + classloader_type = ClassLoader::APP_LOADER; + } else { + // This class won't be archived, so no need to update its + // classloader_type/classpath_index. + return; + } + } + ik->set_shared_class_loader_type(classloader_type); + + if (HeapShared::is_lambda_proxy_klass(ik)) { + InstanceKlass* nest_host = ik->nest_host_not_null(); + ik->set_shared_classpath_index(nest_host->shared_classpath_index()); + } else if (HeapShared::is_lambda_form_klass(ik)) { + ik->set_shared_classpath_index(0); + } else { + // Generated invoker classes. + if (classloader_type == ClassLoader::APP_LOADER) { + ik->set_shared_classpath_index(ClassLoaderExt::app_class_paths_start_index()); + } else { + ik->set_shared_classpath_index(0); + } + } +} #endif // INCLUDE_CDS // Initialize the class loader's access to methods in libzip. Parse and diff --git a/src/hotspot/share/classfile/classLoader.hpp b/src/hotspot/share/classfile/classLoader.hpp index e44059b7247..d6780054904 100644 --- a/src/hotspot/share/classfile/classLoader.hpp +++ b/src/hotspot/share/classfile/classLoader.hpp @@ -140,6 +140,7 @@ public: class ClassLoader: AllStatic { public: enum ClassLoaderType { + OTHER = 0, BOOT_LOADER = 1, /* boot loader */ PLATFORM_LOADER = 2, /* PlatformClassLoader */ APP_LOADER = 3 /* AppClassLoader */ @@ -385,6 +386,7 @@ class ClassLoader: AllStatic { static char* uri_to_path(const char* uri); static void record_result(JavaThread* current, InstanceKlass* ik, const ClassFileStream* stream, bool redefined); + static void record_hidden_class(InstanceKlass* ik); #endif static char* lookup_vm_options(); diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index e17de0f2264..12d3ef79cd0 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -867,6 +867,7 @@ int java_lang_Class::_name_offset; int java_lang_Class::_source_file_offset; int java_lang_Class::_classData_offset; int java_lang_Class::_classRedefinedCount_offset; +int java_lang_Class::_reflectionData_offset; bool java_lang_Class::_offsets_computed = false; GrowableArray* java_lang_Class::_fixup_mirror_list = nullptr; @@ -1303,6 +1304,11 @@ void java_lang_Class::set_class_data(oop java_class, oop class_data) { java_class->obj_field_put(_classData_offset, class_data); } +void java_lang_Class::set_reflection_data(oop java_class, oop reflection_data) { + assert(_reflectionData_offset != 0, "must be set"); + java_class->obj_field_put(_reflectionData_offset, reflection_data); +} + void java_lang_Class::set_class_loader(oop java_class, oop loader) { assert(_class_loader_offset != 0, "offsets should have been initialized"); java_class->obj_field_put(_class_loader_offset, loader); @@ -1493,6 +1499,7 @@ oop java_lang_Class::primitive_mirror(BasicType t) { macro(_module_offset, k, "module", module_signature, false); \ macro(_name_offset, k, "name", string_signature, false); \ macro(_classData_offset, k, "classData", object_signature, false); \ + macro(_reflectionData_offset, k, "reflectionData", java_lang_ref_SoftReference_signature, false); \ macro(_signers_offset, k, "signers", object_array_signature, false); void java_lang_Class::compute_offsets() { @@ -5479,20 +5486,18 @@ void JavaClasses::serialize_offsets(SerializeClosure* soc) { bool JavaClasses::is_supported_for_archiving(oop obj) { Klass* klass = obj->klass(); - if (klass == vmClasses::ClassLoader_klass() || // ClassLoader::loader_data is malloc'ed. - // The next 3 classes are used to implement java.lang.invoke, and are not used directly in - // regular Java code. The implementation of java.lang.invoke uses generated hidden classes - // (e.g., as referenced by ResolvedMethodName::vmholder) that are not yet supported by CDS. - // So for now we cannot not support these classes for archiving. - // - // These objects typically are not referenced by static fields, but rather by resolved - // constant pool entries, so excluding them shouldn't affect the archiving of static fields. - klass == vmClasses::ResolvedMethodName_klass() || - klass == vmClasses::MemberName_klass() || - klass == vmClasses::Context_klass() || - // It's problematic to archive Reference objects. One of the reasons is that - // Reference::discovered may pull in unwanted objects (see JDK-8284336) - klass->is_subclass_of(vmClasses::Reference_klass())) { + if (!CDSConfig::is_dumping_invokedynamic()) { + // These are supported by CDS only when CDSConfig::is_dumping_invokedynamic() is enabled. + if (klass == vmClasses::ResolvedMethodName_klass() || + klass == vmClasses::MemberName_klass() || + klass == vmClasses::Context_klass()) { + return false; + } + } + + if (klass->is_subclass_of(vmClasses::Reference_klass())) { + // It's problematic to archive Reference objects. One of the reasons is that + // Reference::discovered may pull in unwanted objects (see JDK-8284336) return false; } diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index a2e6ad7349c..dcb94878aed 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -254,6 +254,7 @@ class java_lang_Class : AllStatic { static int _source_file_offset; static int _classData_offset; static int _classRedefinedCount_offset; + static int _reflectionData_offset; static bool _offsets_computed; @@ -321,6 +322,7 @@ class java_lang_Class : AllStatic { static objArrayOop signers(oop java_class); static oop class_data(oop java_class); static void set_class_data(oop java_class, oop classData); + static void set_reflection_data(oop java_class, oop reflection_data); static int component_mirror_offset() { return _component_mirror_offset; } diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 2d0ebfb7266..b9b341c4dab 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -82,6 +82,7 @@ #include "services/diagnosticCommand.hpp" #include "services/finalizerService.hpp" #include "services/threadService.hpp" +#include "utilities/growableArray.hpp" #include "utilities/macros.hpp" #include "utilities/utf8.hpp" #if INCLUDE_CDS @@ -134,19 +135,6 @@ oop SystemDictionary::java_platform_loader() { } void SystemDictionary::compute_java_loaders(TRAPS) { - if (_java_system_loader.is_empty()) { - oop system_loader = get_system_class_loader_impl(CHECK); - _java_system_loader = OopHandle(Universe::vm_global(), system_loader); - } else { - // It must have been restored from the archived module graph - assert(CDSConfig::is_using_archive(), "must be"); - assert(CDSConfig::is_using_full_module_graph(), "must be"); - DEBUG_ONLY( - oop system_loader = get_system_class_loader_impl(CHECK); - assert(_java_system_loader.resolve() == system_loader, "must be"); - ) - } - if (_java_platform_loader.is_empty()) { oop platform_loader = get_platform_class_loader_impl(CHECK); _java_platform_loader = OopHandle(Universe::vm_global(), platform_loader); @@ -158,6 +146,19 @@ void SystemDictionary::compute_java_loaders(TRAPS) { oop platform_loader = get_platform_class_loader_impl(CHECK); assert(_java_platform_loader.resolve() == platform_loader, "must be"); ) + } + + if (_java_system_loader.is_empty()) { + oop system_loader = get_system_class_loader_impl(CHECK); + _java_system_loader = OopHandle(Universe::vm_global(), system_loader); + } else { + // It must have been restored from the archived module graph + assert(CDSConfig::is_using_archive(), "must be"); + assert(CDSConfig::is_using_full_module_graph(), "must be"); + DEBUG_ONLY( + oop system_loader = get_system_class_loader_impl(CHECK); + assert(_java_system_loader.resolve() == system_loader, "must be"); + ) } } @@ -1718,6 +1719,23 @@ void SystemDictionary::update_dictionary(JavaThread* current, mu1.notify_all(); } +#if INCLUDE_CDS +// Indicate that loader_data has initiated the loading of class k, which +// has already been defined by a parent loader. +// This API should be used only by AOTLinkedClassBulkLoader +void SystemDictionary::add_to_initiating_loader(JavaThread* current, + InstanceKlass* k, + ClassLoaderData* loader_data) { + assert(CDSConfig::is_using_aot_linked_classes(), "must be"); + assert_locked_or_safepoint(SystemDictionary_lock); + Symbol* name = k->name(); + Dictionary* dictionary = loader_data->dictionary(); + assert(k->is_loaded(), "must be"); + assert(k->class_loader_data() != loader_data, "only for classes defined by a parent loader"); + assert(dictionary->find_class(current, name) == nullptr, "sanity"); + dictionary->add_klass(current, name, k); +} +#endif // Try to find a class name using the loader constraints. The // loader constraints might know about a class that isn't fully loaded @@ -2033,6 +2051,52 @@ Method* SystemDictionary::find_method_handle_intrinsic(vmIntrinsicID iid, return nullptr; } +#if INCLUDE_CDS +void SystemDictionary::get_all_method_handle_intrinsics(GrowableArray* methods) { + assert(SafepointSynchronize::is_at_safepoint(), "must be"); + auto do_method = [&] (InvokeMethodKey& key, Method*& m) { + methods->append(m); + }; + _invoke_method_intrinsic_table->iterate_all(do_method); +} + +void SystemDictionary::restore_archived_method_handle_intrinsics() { + if (UseSharedSpaces) { + EXCEPTION_MARK; + restore_archived_method_handle_intrinsics_impl(THREAD); + if (HAS_PENDING_EXCEPTION) { + // This is probably caused by OOM -- other parts of the CDS archive have direct pointers to + // the archived method handle intrinsics, so we can't really recover from this failure. + vm_exit_during_initialization(err_msg("Failed to restore archived method handle intrinsics. Try to increase heap size.")); + } + } +} + +void SystemDictionary::restore_archived_method_handle_intrinsics_impl(TRAPS) { + Array* list = MetaspaceShared::archived_method_handle_intrinsics(); + for (int i = 0; i < list->length(); i++) { + methodHandle m(THREAD, list->at(i)); + Method::restore_archived_method_handle_intrinsic(m, CHECK); + m->constants()->restore_unshareable_info(CHECK); + if (!Arguments::is_interpreter_only() || m->intrinsic_id() == vmIntrinsics::_linkToNative) { + AdapterHandlerLibrary::create_native_wrapper(m); + if (!m->has_compiled_code()) { + ResourceMark rm(THREAD); + vm_exit_during_initialization(err_msg("Failed to initialize method %s", m->external_name())); + } + } + + // There's no need to grab the InvokeMethodIntrinsicTable_lock, as we are still very early in + // VM start-up -- in init_globals2() -- so we are still running a single Java thread. It's not + // possible to have a contention. + const int iid_as_int = vmIntrinsics::as_int(m->intrinsic_id()); + InvokeMethodKey key(m->signature(), iid_as_int); + bool created = _invoke_method_intrinsic_table->put(key, m()); + assert(created, "unexpected contention"); + } +} +#endif // INCLUDE_CDS + // Helper for unpacking the return value from linkMethod and linkCallSite. static Method* unpack_method_and_appendix(Handle mname, Klass* accessing_klass, diff --git a/src/hotspot/share/classfile/systemDictionary.hpp b/src/hotspot/share/classfile/systemDictionary.hpp index 04980291716..9f5f34ee773 100644 --- a/src/hotspot/share/classfile/systemDictionary.hpp +++ b/src/hotspot/share/classfile/systemDictionary.hpp @@ -75,7 +75,10 @@ class GCTimer; class EventClassLoad; class Symbol; +template class GrowableArray; + class SystemDictionary : AllStatic { + friend class AOTLinkedClassBulkLoader; friend class BootstrapInfo; friend class vmClasses; friend class VMStructs; @@ -239,6 +242,9 @@ public: Symbol* signature, TRAPS); + static void get_all_method_handle_intrinsics(GrowableArray* methods) NOT_CDS_RETURN; + static void restore_archived_method_handle_intrinsics() NOT_CDS_RETURN; + // compute java_mirror (java.lang.Class instance) for a type ("I", "[[B", "LFoo;", etc.) // Either the accessing_klass or the CL/PD can be non-null, but not both. static Handle find_java_mirror_for_type(Symbol* signature, @@ -293,6 +299,9 @@ public: const char* message); static const char* find_nest_host_error(const constantPoolHandle& pool, int which); + static void add_to_initiating_loader(JavaThread* current, InstanceKlass* k, + ClassLoaderData* loader_data) NOT_CDS_RETURN; + static OopHandle _java_system_loader; static OopHandle _java_platform_loader; @@ -331,6 +340,7 @@ private: Handle protection_domain, TRAPS); // Second part of load_shared_class static void load_shared_class_misc(InstanceKlass* ik, ClassLoaderData* loader_data) NOT_CDS_RETURN; + static void restore_archived_method_handle_intrinsics_impl(TRAPS) NOT_CDS_RETURN; protected: // Used by SystemDictionaryShared diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 08f224d969f..84b7ea0d6b9 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -27,12 +27,13 @@ #include "cds/archiveHeapLoader.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" +#include "cds/cdsProtectionDomain.hpp" #include "cds/classListParser.hpp" #include "cds/classListWriter.hpp" +#include "cds/dumpTimeClassInfo.inline.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" -#include "cds/cdsProtectionDomain.hpp" -#include "cds/dumpTimeClassInfo.inline.hpp" +#include "cds/heapShared.hpp" #include "cds/metaspaceShared.hpp" #include "cds/runTimeClassInfo.hpp" #include "classfile/classFileStream.hpp" @@ -204,6 +205,15 @@ DumpTimeClassInfo* SystemDictionaryShared::get_info_locked(InstanceKlass* k) { return info; } +void SystemDictionaryShared::mark_required_hidden_class(InstanceKlass* k) { + assert(k->is_hidden(), "sanity"); + DumpTimeClassInfo* info = _dumptime_table->get(k); + ResourceMark rm; + if (info != nullptr) { + info->set_is_required_hidden_class(); + } +} + bool SystemDictionaryShared::check_for_exclusion(InstanceKlass* k, DumpTimeClassInfo* info) { if (MetaspaceShared::is_in_shared_metaspace(k)) { // We have reached a super type that's already in the base archive. Treat it @@ -287,9 +297,13 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { if (!k->is_hidden() && k->shared_classpath_index() < 0 && is_builtin(k)) { if (k->name()->starts_with("java/lang/invoke/BoundMethodHandle$Species_")) { // This class is dynamically generated by the JDK - ResourceMark rm; - log_info(cds)("Skipping %s because it is dynamically generated", k->name()->as_C_string()); - return true; // exclude without warning + if (CDSConfig::is_dumping_aot_linked_classes()) { + k->set_shared_classpath_index(0); + } else { + ResourceMark rm; + log_info(cds)("Skipping %s because it is dynamically generated", k->name()->as_C_string()); + return true; // exclude without warning + } } else { // These are classes loaded from unsupported locations (such as those loaded by JVMTI native // agent during dump time). @@ -326,9 +340,8 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { } } - if (k->is_hidden() && !is_registered_lambda_proxy_class(k)) { - ResourceMark rm; - log_debug(cds)("Skipping %s: Hidden class", k->name()->as_C_string()); + if (k->is_hidden() && !should_hidden_class_be_archived(k)) { + log_info(cds)("Skipping %s: Hidden class", k->name()->as_C_string()); return true; } @@ -588,7 +601,11 @@ void SystemDictionaryShared::validate_before_archiving(InstanceKlass* k) { guarantee(!info->is_excluded(), "Should not attempt to archive excluded class %s", name); if (is_builtin(k)) { if (k->is_hidden()) { - assert(is_registered_lambda_proxy_class(k), "unexpected hidden class %s", name); + if (CDSConfig::is_dumping_invokedynamic()) { + assert(should_hidden_class_be_archived(k), "unexpected hidden class %s", name); + } else { + assert(is_registered_lambda_proxy_class(k), "unexpected hidden class %s", name); + } } guarantee(!k->is_shared_unregistered_class(), "Class loader type must be set for BUILTIN class %s", name); @@ -640,7 +657,93 @@ public: } }; -void SystemDictionaryShared::check_excluded_classes() { +void SystemDictionaryShared::scan_constant_pool(InstanceKlass* k) { + if (CDSConfig::is_dumping_invokedynamic()) { + k->constants()->find_required_hidden_classes(); + } +} + +bool SystemDictionaryShared::should_hidden_class_be_archived(InstanceKlass* k) { + assert(k->is_hidden(), "sanity"); + if (is_registered_lambda_proxy_class(k)) { + return true; + } + + if (CDSConfig::is_dumping_invokedynamic()) { + DumpTimeClassInfo* info = _dumptime_table->get(k); + if (info != nullptr && info->is_required_hidden_class()) { + guarantee(HeapShared::is_archivable_hidden_klass(k), "required hidden class must be archivable"); + return true; + } + } + + return false; +} + +// Returns true if the class should be excluded. This can be called by +// AOTConstantPoolResolver before or after we enter the CDS safepoint. +// When called before the safepoint, we need to link the class so that +// it can be checked by check_for_exclusion(). +bool SystemDictionaryShared::should_be_excluded(Klass* k) { + assert(CDSConfig::is_dumping_archive(), "sanity"); + assert(CDSConfig::current_thread_is_vm_or_dumper(), "sanity"); + + if (k->is_objArray_klass()) { + return should_be_excluded(ObjArrayKlass::cast(k)->bottom_klass()); + } + + if (!k->is_instance_klass()) { + return false; + } else { + InstanceKlass* ik = InstanceKlass::cast(k); + + if (!SafepointSynchronize::is_at_safepoint()) { + if (!ik->is_linked()) { + // check_for_exclusion() below doesn't link unlinked classes. We come + // here only when we are trying to aot-link constant pool entries, so + // we'd better link the class. + JavaThread* THREAD = JavaThread::current(); + ik->link_class(THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + return true; // linking failed -- let's exclude it + } + } + + MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); + DumpTimeClassInfo* p = get_info_locked(ik); + if (p->is_excluded()) { + return true; + } + return check_for_exclusion(ik, p); + } else { + // No need to check for is_linked() as all eligible classes should have + // already been linked in MetaspaceShared::link_class_for_cds(). + // Can't take the lock as we are in safepoint. + DumpTimeClassInfo* p = _dumptime_table->get(ik); + if (p->is_excluded()) { + return true; + } + return check_for_exclusion(ik, p); + } + } +} + +void SystemDictionaryShared::find_all_archivable_classes() { + HeapShared::start_finding_required_hidden_classes(); + find_all_archivable_classes_impl(); + HeapShared::end_finding_required_hidden_classes(); +} + +// Iterate over all the classes in _dumptime_table, marking the ones that must be +// excluded from the archive. Those that are not excluded will be archivable. +// +// (a) Non-hidden classes are easy. They are only check by the rules in +// SystemDictionaryShared::check_for_exclusion(). +// (b) For hidden classes, we only archive those that are required (i.e., they are +// referenced by Java objects (such as CallSites) that are reachable from +// ConstantPools). This needs help from HeapShared. +void SystemDictionaryShared::find_all_archivable_classes_impl() { assert(!class_loading_may_happen(), "class loading must be disabled"); assert_lock_strong(DumpTimeTable_lock); @@ -653,10 +756,56 @@ void SystemDictionaryShared::check_excluded_classes() { dup_checker.mark_duplicated_classes(); } - auto check_for_exclusion = [&] (InstanceKlass* k, DumpTimeClassInfo& info) { - SystemDictionaryShared::check_for_exclusion(k, &info); + ResourceMark rm; + + // First, scan all non-hidden classes + auto check_non_hidden = [&] (InstanceKlass* k, DumpTimeClassInfo& info) { + if (!k->is_hidden()) { + SystemDictionaryShared::check_for_exclusion(k, &info); + if (!info.is_excluded() && !info.has_scanned_constant_pool()) { + scan_constant_pool(k); + info.set_has_scanned_constant_pool(); + } + } }; - _dumptime_table->iterate_all_live_classes(check_for_exclusion); + _dumptime_table->iterate_all_live_classes(check_non_hidden); + + // Then, scan all the hidden classes that have been marked as required to + // discover more hidden classes. Stop when we cannot make progress anymore. + bool made_progress; + do { + made_progress = false; + auto check_hidden = [&] (InstanceKlass* k, DumpTimeClassInfo& info) { + if (k->is_hidden() && should_hidden_class_be_archived(k)) { + SystemDictionaryShared::check_for_exclusion(k, &info); + if (info.is_excluded()) { + guarantee(!info.is_required_hidden_class(), "A required hidden class cannot be marked as excluded"); + } else if (!info.has_scanned_constant_pool()) { + scan_constant_pool(k); + info.set_has_scanned_constant_pool(); + // The CP entries in k *MAY* refer to other hidden classes, so scan + // every hidden class again. + made_progress = true; + } + } + }; + _dumptime_table->iterate_all_live_classes(check_hidden); + } while (made_progress); + + // Now, all hidden classes that have not yet been scanned must be marked as excluded + auto exclude_remaining_hidden = [&] (InstanceKlass* k, DumpTimeClassInfo& info) { + if (k->is_hidden()) { + SystemDictionaryShared::check_for_exclusion(k, &info); + if (CDSConfig::is_dumping_invokedynamic()) { + if (should_hidden_class_be_archived(k)) { + guarantee(!info.is_excluded(), "Must be"); + } else { + guarantee(info.is_excluded(), "Must be"); + } + } + } + }; + _dumptime_table->iterate_all_live_classes(exclude_remaining_hidden); _dumptime_table->update_counts(); cleanup_lambda_proxy_class_dictionary(); @@ -766,6 +915,11 @@ void SystemDictionaryShared::add_lambda_proxy_class(InstanceKlass* caller_ik, Method* member_method, Symbol* instantiated_method_type, TRAPS) { + if (CDSConfig::is_dumping_invokedynamic()) { + // The lambda proxy classes will be stored as part of aot-resolved constant pool entries. + // There's no need to remember them in a separate table. + return; + } assert(caller_ik->class_loader() == lambda_ik->class_loader(), "mismatched class loader"); assert(caller_ik->class_loader_data() == lambda_ik->class_loader_data(), "mismatched class loader data"); @@ -1188,7 +1342,7 @@ public: // In static dump, info._proxy_klasses->at(0) is already relocated to point to the archived class // (not the original class). // - // The following check has been moved to SystemDictionaryShared::check_excluded_classes(), which + // The following check has been moved to SystemDictionaryShared::find_all_archivable_classes(), which // happens before the classes are copied. // // if (SystemDictionaryShared::is_excluded_class(info._proxy_klasses->at(0))) { diff --git a/src/hotspot/share/classfile/systemDictionaryShared.hpp b/src/hotspot/share/classfile/systemDictionaryShared.hpp index c79821036e0..5e0c54a6ffc 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp @@ -188,6 +188,7 @@ private: static DumpTimeClassInfo* get_info(InstanceKlass* k); static DumpTimeClassInfo* get_info_locked(InstanceKlass* k); + static void find_all_archivable_classes_impl(); static void write_dictionary(RunTimeSharedDictionary* dictionary, bool is_builtin); static void write_lambda_proxy_class_dictionary(LambdaProxyClassDictionary* dictionary); @@ -199,10 +200,12 @@ private: 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); - + static void scan_constant_pool(InstanceKlass* k); DEBUG_ONLY(static bool _class_loading_may_happen;) public: + static bool should_hidden_class_be_archived(InstanceKlass* k); + static void mark_required_hidden_class(InstanceKlass* k); static bool is_hidden_lambda_proxy(InstanceKlass* ik); static bool is_early_klass(InstanceKlass* k); // Was k loaded while JvmtiExport::is_early_phase()==true static bool has_archived_enum_objs(InstanceKlass* ik); @@ -288,7 +291,8 @@ public: } static bool add_unregistered_class(Thread* current, InstanceKlass* k); - static void check_excluded_classes(); + static void find_all_archivable_classes(); + static bool should_be_excluded(Klass* k); static bool check_for_exclusion(InstanceKlass* k, DumpTimeClassInfo* info); static void validate_before_archiving(InstanceKlass* k); static bool is_excluded_class(InstanceKlass* k); diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 395e718c55d..46d601d17dd 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -175,6 +175,7 @@ do_klass(Short_klass, java_lang_Short ) \ do_klass(Integer_klass, java_lang_Integer ) \ do_klass(Long_klass, java_lang_Long ) \ + do_klass(Void_klass, java_lang_Void ) \ \ /* force inline of iterators */ \ do_klass(Iterator_klass, java_util_Iterator ) \ diff --git a/src/hotspot/share/classfile/vmClasses.cpp b/src/hotspot/share/classfile/vmClasses.cpp index b62d699dfe2..553864ef0b9 100644 --- a/src/hotspot/share/classfile/vmClasses.cpp +++ b/src/hotspot/share/classfile/vmClasses.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveHeapLoader.hpp" #include "cds/cdsConfig.hpp" #include "classfile/classLoader.hpp" @@ -210,6 +211,9 @@ void vmClasses::resolve_all(TRAPS) { #endif InstanceStackChunkKlass::init_offset_of_stack(); + if (CDSConfig::is_using_aot_linked_classes()) { + AOTLinkedClassBulkLoader::load_javabase_classes(THREAD); + } } #if INCLUDE_CDS diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 34861b52d54..0728e156f82 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -89,6 +89,7 @@ class SerializeClosure; template(java_lang_Integer_IntegerCache, "java/lang/Integer$IntegerCache") \ template(java_lang_Long, "java/lang/Long") \ template(java_lang_Long_LongCache, "java/lang/Long$LongCache") \ + template(java_lang_Void, "java/lang/Void") \ \ template(jdk_internal_vm_vector_VectorSupport, "jdk/internal/vm/vector/VectorSupport") \ template(jdk_internal_vm_vector_VectorPayload, "jdk/internal/vm/vector/VectorSupport$VectorPayload") \ @@ -309,6 +310,8 @@ class SerializeClosure; template(jdk_internal_vm_annotation_JvmtiHideEvents_signature, "Ljdk/internal/vm/annotation/JvmtiHideEvents;") \ template(jdk_internal_vm_annotation_JvmtiMountTransition_signature, "Ljdk/internal/vm/annotation/JvmtiMountTransition;") \ \ + template(java_lang_ref_SoftReference_signature, "Ljava/lang/ref/SoftReference;") \ + \ /* Support for JSR 292 & invokedynamic (JDK 1.7 and above) */ \ template(java_lang_invoke_CallSite, "java/lang/invoke/CallSite") \ template(java_lang_invoke_ConstantCallSite, "java/lang/invoke/ConstantCallSite") \ @@ -726,6 +729,7 @@ class SerializeClosure; JFR_TEMPLATES(template) \ \ /* CDS */ \ + template(createArchivedObjects, "createArchivedObjects") \ template(dumpSharedArchive, "dumpSharedArchive") \ template(dumpSharedArchive_signature, "(ZLjava/lang/String;)Ljava/lang/String;") \ template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \ @@ -739,6 +743,7 @@ class SerializeClosure; template(jdk_internal_misc_CDS, "jdk/internal/misc/CDS") \ template(java_util_concurrent_ConcurrentHashMap, "java/util/concurrent/ConcurrentHashMap") \ template(java_util_ArrayList, "java/util/ArrayList") \ + template(runtimeSetup, "runtimeSetup") \ template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ template(url_array_classloader_void_signature, "([Ljava/net/URL;Ljava/lang/ClassLoader;)V") \ diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index 1d8268dbda6..cb85513eed0 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -949,6 +949,15 @@ void InterpreterRuntime::resolve_invokehandle(JavaThread* current) { pool->cache()->set_method_handle(method_index, info); } +void InterpreterRuntime::cds_resolve_invokehandle(int raw_index, + constantPoolHandle& pool, TRAPS) { + const Bytecodes::Code bytecode = Bytecodes::_invokehandle; + CallInfo info; + LinkResolver::resolve_invoke(info, Handle(), pool, raw_index, bytecode, CHECK); + + pool->cache()->set_method_handle(raw_index, info); +} + // First time execution: Resolve symbols, create a permanent CallSite object. void InterpreterRuntime::resolve_invokedynamic(JavaThread* current) { LastFrameAccessor last_frame(current); @@ -968,6 +977,14 @@ void InterpreterRuntime::resolve_invokedynamic(JavaThread* current) { pool->cache()->set_dynamic_call(info, index); } +void InterpreterRuntime::cds_resolve_invokedynamic(int raw_index, + constantPoolHandle& pool, TRAPS) { + const Bytecodes::Code bytecode = Bytecodes::_invokedynamic; + CallInfo info; + LinkResolver::resolve_invoke(info, Handle(), pool, raw_index, bytecode, CHECK); + pool->cache()->set_dynamic_call(info, raw_index); +} + // This function is the interface to the assembly code. It returns the resolved // cpCache entry. This doesn't safepoint, but the helper routines safepoint. // This function will check for redefinition! diff --git a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp index 61041694fc6..3635433f434 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.hpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.hpp @@ -92,12 +92,15 @@ class InterpreterRuntime: AllStatic { static void resolve_from_cache(JavaThread* current, Bytecodes::Code bytecode); - // Used by ClassPrelinker + // Used by AOTConstantPoolResolver static void resolve_get_put(Bytecodes::Code bytecode, int field_index, methodHandle& m, constantPoolHandle& pool, bool initialize_holder, TRAPS); static void cds_resolve_invoke(Bytecodes::Code bytecode, int method_index, constantPoolHandle& pool, TRAPS); - + static void cds_resolve_invokehandle(int raw_index, + constantPoolHandle& pool, TRAPS); + static void cds_resolve_invokedynamic(int raw_index, + constantPoolHandle& pool, TRAPS); private: // Statics & fields static void resolve_get_put(JavaThread* current, Bytecodes::Code bytecode); diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index b53a015b0f2..d9563e41ce7 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -38,6 +38,7 @@ class outputStream; LOG_TAG(age) \ LOG_TAG(alloc) \ LOG_TAG(annotation) \ + LOG_TAG(aot) \ LOG_TAG(arguments) \ LOG_TAG(array) \ LOG_TAG(attach) \ diff --git a/src/hotspot/share/memory/iterator.inline.hpp b/src/hotspot/share/memory/iterator.inline.hpp index 532782b18f1..7ed2b9b3faa 100644 --- a/src/hotspot/share/memory/iterator.inline.hpp +++ b/src/hotspot/share/memory/iterator.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, 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 @@ -27,6 +27,7 @@ #include "memory/iterator.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "classfile/classLoaderData.hpp" #include "code/nmethod.hpp" #include "oops/access.inline.hpp" @@ -51,7 +52,11 @@ inline void ClaimMetadataVisitingOopIterateClosure::do_cld(ClassLoaderData* cld) inline void ClaimMetadataVisitingOopIterateClosure::do_klass(Klass* k) { ClassLoaderData* cld = k->class_loader_data(); - ClaimMetadataVisitingOopIterateClosure::do_cld(cld); + if (cld != nullptr) { + ClaimMetadataVisitingOopIterateClosure::do_cld(cld); + } else { + assert(AOTLinkedClassBulkLoader::is_pending_aot_linked_class(k), "sanity"); + } } inline void ClaimMetadataVisitingOopIterateClosure::do_nmethod(nmethod* nm) { diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 7147e2588c9..a435d6e5fd3 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -23,11 +23,11 @@ */ #include "precompiled.hpp" -#include "cds/archiveHeapWriter.hpp" -#include "cds/archiveHeapLoader.hpp" +#include "cds/aotConstantPoolResolver.hpp" #include "cds/archiveBuilder.hpp" +#include "cds/archiveHeapLoader.hpp" +#include "cds/archiveHeapWriter.hpp" #include "cds/cdsConfig.hpp" -#include "cds/classPrelinker.hpp" #include "cds/heapShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" @@ -35,6 +35,7 @@ #include "classfile/metadataOnStackMark.hpp" #include "classfile/stringTable.hpp" #include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" #include "code/codeCache.hpp" @@ -283,6 +284,44 @@ void ConstantPool::klass_at_put(int class_index, Klass* k) { } #if INCLUDE_CDS_JAVA_HEAP +template +void ConstantPool::iterate_archivable_resolved_references(Function function) { + objArrayOop rr = resolved_references(); + if (rr != nullptr && cache() != nullptr && CDSConfig::is_dumping_invokedynamic()) { + Array* indy_entries = cache()->resolved_indy_entries(); + if (indy_entries != nullptr) { + for (int i = 0; i < indy_entries->length(); i++) { + ResolvedIndyEntry *rie = indy_entries->adr_at(i); + if (rie->is_resolved() && AOTConstantPoolResolver::is_resolution_deterministic(this, rie->constant_pool_index())) { + int rr_index = rie->resolved_references_index(); + assert(resolved_reference_at(rr_index) != nullptr, "must exist"); + function(rr_index); + + // Save the BSM as well (sometimes the JIT looks up the BSM it for replay) + int indy_cp_index = rie->constant_pool_index(); + int bsm_mh_cp_index = bootstrap_method_ref_index_at(indy_cp_index); + int bsm_rr_index = cp_to_object_index(bsm_mh_cp_index); + assert(resolved_reference_at(bsm_rr_index) != nullptr, "must exist"); + function(bsm_rr_index); + } + } + } + + Array* method_entries = cache()->resolved_method_entries(); + if (method_entries != nullptr) { + for (int i = 0; i < method_entries->length(); i++) { + ResolvedMethodEntry* rme = method_entries->adr_at(i); + if (rme->is_resolved(Bytecodes::_invokehandle) && rme->has_appendix() && + cache()->can_archive_resolved_method(this, rme)) { + int rr_index = rme->resolved_references_index(); + assert(resolved_reference_at(rr_index) != nullptr, "must exist"); + function(rr_index); + } + } + } + } +} + // Returns the _resolved_reference array after removing unarchivable items from it. // Returns null if this class is not supported, or _resolved_reference doesn't exist. objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { @@ -300,8 +339,15 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { objArrayOop rr = resolved_references(); if (rr != nullptr) { + ResourceMark rm; int rr_len = rr->length(); + GrowableArray keep_resolved_refs(rr_len, rr_len, false); + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); + src_cp->iterate_archivable_resolved_references([&](int rr_index) { + keep_resolved_refs.at_put(rr_index, true); + }); + objArrayOop scratch_rr = HeapShared::scratch_resolved_references(src_cp); Array* ref_map = reference_map(); int ref_map_len = ref_map == nullptr ? 0 : ref_map->length(); @@ -316,8 +362,13 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { if (!ArchiveHeapWriter::is_string_too_large_to_archive(obj)) { scratch_rr->obj_at_put(i, obj); } + continue; } } + + if (keep_resolved_refs.at(i)) { + scratch_rr->obj_at_put(i, obj); + } } } return scratch_rr; @@ -325,6 +376,32 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { return rr; } +void ConstantPool::find_required_hidden_classes() { + if (_cache == nullptr) { + return; + } + + ClassLoaderData* loader_data = pool_holder()->class_loader_data(); + if (loader_data == nullptr) { + // These are custom loader classes from the preimage + return; + } + + if (!SystemDictionaryShared::is_builtin_loader(loader_data)) { + // Archiving resolved references for classes from non-builtin loaders + // is not yet supported. + return; + } + + objArrayOop rr = resolved_references(); + if (rr != nullptr) { + iterate_archivable_resolved_references([&](int rr_index) { + oop obj = rr->obj_at(rr_index); + HeapShared::find_required_hidden_classes_in_object(obj); + }); + } +} + void ConstantPool::add_dumped_interned_strings() { objArrayOop rr = resolved_references(); if (rr != nullptr) { @@ -349,6 +426,11 @@ void ConstantPool::restore_unshareable_info(TRAPS) { assert(is_constantPool(), "ensure C++ vtable is restored"); assert(on_stack(), "should always be set for shared constant pools"); assert(is_shared(), "should always be set for shared constant pools"); + if (is_for_method_handle_intrinsic()) { + // See the same check in remove_unshareable_info() below. + assert(cache() == NULL, "must not have cpCache"); + return; + } assert(_cache != nullptr, "constant pool _cache should not be null"); // Only create the new resolved references array if it hasn't been attempted before @@ -388,6 +470,14 @@ void ConstantPool::remove_unshareable_info() { // we always set _on_stack to true to avoid having to change _flags during runtime. _flags |= (_on_stack | _is_shared); + if (is_for_method_handle_intrinsic()) { + // This CP was created by Method::make_method_handle_intrinsic() and has nothing + // that need to be removed/restored. It has no cpCache since the intrinsic methods + // don't have any bytecodes. + assert(cache() == NULL, "must not have cpCache"); + return; + } + // 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) { @@ -482,7 +572,7 @@ void ConstantPool::remove_resolved_klass_if_non_deterministic(int cp_index) { can_archive = false; } else { ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); - can_archive = ClassPrelinker::is_resolution_deterministic(src_cp, cp_index); + can_archive = AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index); } if (!can_archive) { @@ -502,7 +592,7 @@ void ConstantPool::remove_resolved_klass_if_non_deterministic(int cp_index) { (!k->is_instance_klass() || pool_holder()->is_subtype_of(k)) ? "" : " (not supertype)"); } else { Symbol* name = klass_name_at(cp_index); - log.print(" %s", name->as_C_string()); + log.print(" => %s", name->as_C_string()); } } @@ -748,9 +838,7 @@ int ConstantPool::to_cp_index(int index, Bytecodes::Code code) { case Bytecodes::_fast_invokevfinal: // Bytecode interpreter uses this return resolved_method_entry_at(index)->constant_pool_index(); default: - tty->print_cr("Unexpected bytecode: %d", code); - ShouldNotReachHere(); // All cases should have been handled - return -1; + fatal("Unexpected bytecode: %s", Bytecodes::name(code)); } } diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index bcc9a08dd6c..9ada3e29d49 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -82,7 +82,7 @@ class ConstantPool : public Metadata { friend class JVMCIVMStructs; friend class BytecodeInterpreter; // Directly extracts a klass in the pool for fast instanceof/checkcast friend class Universe; // For null constructor - friend class ClassPrelinker; // CDS + friend class AOTConstantPoolResolver; private: // If you add a new field that points to any metaspace object, you // must add this field to ConstantPool::metaspace_pointers_do(). @@ -109,7 +109,8 @@ class ConstantPool : public Metadata { _has_preresolution = 1, // Flags _on_stack = 2, _is_shared = 4, - _has_dynamic_constant = 8 + _has_dynamic_constant = 8, + _is_for_method_handle_intrinsic = 16 }; u2 _flags; // old fashioned bit twiddling @@ -216,6 +217,9 @@ class ConstantPool : public Metadata { bool has_dynamic_constant() const { return (_flags & _has_dynamic_constant) != 0; } void set_has_dynamic_constant() { _flags |= _has_dynamic_constant; } + bool is_for_method_handle_intrinsic() const { return (_flags & _is_for_method_handle_intrinsic) != 0; } + void set_is_for_method_handle_intrinsic() { _flags |= _is_for_method_handle_intrinsic; } + // Klass holding pool InstanceKlass* pool_holder() const { return _pool_holder; } void set_pool_holder(InstanceKlass* k) { _pool_holder = k; } @@ -679,12 +683,14 @@ class ConstantPool : public Metadata { #if INCLUDE_CDS // CDS support objArrayOop prepare_resolved_references_for_archiving() NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + void find_required_hidden_classes() NOT_CDS_JAVA_HEAP_RETURN; void add_dumped_interned_strings() NOT_CDS_JAVA_HEAP_RETURN; void remove_unshareable_info(); void restore_unshareable_info(TRAPS); private: void remove_unshareable_entries(); void remove_resolved_klass_if_non_deterministic(int cp_index); + template void iterate_archivable_resolved_references(Function function); #endif private: diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 321d8add755..bcf3669c9df 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -23,9 +23,9 @@ */ #include "precompiled.hpp" +#include "cds/aotConstantPoolResolver.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/classPrelinker.hpp" #include "cds/heapShared.hpp" #include "classfile/resolutionErrors.hpp" #include "classfile/systemDictionary.hpp" @@ -401,9 +401,7 @@ void ConstantPoolCache::remove_unshareable_info() { assert(CDSConfig::is_dumping_archive(), "sanity"); if (_resolved_indy_entries != nullptr) { - for (int i = 0; i < _resolved_indy_entries->length(); i++) { - resolved_indy_entry_at(i)->remove_unshareable_info(); - } + remove_resolved_indy_entries_if_non_deterministic(); } if (_resolved_field_entries != nullptr) { remove_resolved_field_entries_if_non_deterministic(); @@ -422,7 +420,7 @@ void ConstantPoolCache::remove_resolved_field_entries_if_non_deterministic() { bool archived = false; bool resolved = rfi->is_resolved(Bytecodes::_getfield) || rfi->is_resolved(Bytecodes::_putfield); - if (resolved && ClassPrelinker::is_resolution_deterministic(src_cp, cp_index)) { + if (resolved && AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index)) { rfi->mark_and_relocate(); archived = true; } else { @@ -436,11 +434,10 @@ void ConstantPoolCache::remove_resolved_field_entries_if_non_deterministic() { Symbol* klass_name = cp->klass_name_at(klass_cp_index); Symbol* name = cp->uncached_name_ref_at(cp_index); Symbol* signature = cp->uncached_signature_ref_at(cp_index); - log.print("%s field CP entry [%3d]: %s %s %s.%s:%s", + log.print("%s field CP entry [%3d]: %s => %s.%s:%s", (archived ? "archived" : "reverted"), cp_index, cp->pool_holder()->name()->as_C_string(), - (archived ? "=>" : " "), klass_name->as_C_string(), name->as_C_string(), signature->as_C_string()); } } @@ -457,13 +454,13 @@ void ConstantPoolCache::remove_resolved_method_entries_if_non_deterministic() { bool archived = false; bool resolved = rme->is_resolved(Bytecodes::_invokevirtual) || rme->is_resolved(Bytecodes::_invokespecial) || - rme->is_resolved(Bytecodes::_invokeinterface); + rme->is_resolved(Bytecodes::_invokeinterface) || + rme->is_resolved(Bytecodes::_invokehandle); // Just for safety -- this should not happen, but do not archive if we ever see this. - resolved &= !(rme->is_resolved(Bytecodes::_invokehandle) || - rme->is_resolved(Bytecodes::_invokestatic)); + resolved &= !(rme->is_resolved(Bytecodes::_invokestatic)); - if (resolved && can_archive_resolved_method(rme)) { + if (resolved && can_archive_resolved_method(src_cp, rme)) { rme->mark_and_relocate(src_cp); archived = true; } else { @@ -495,7 +492,41 @@ void ConstantPoolCache::remove_resolved_method_entries_if_non_deterministic() { } } -bool ConstantPoolCache::can_archive_resolved_method(ResolvedMethodEntry* method_entry) { +void ConstantPoolCache::remove_resolved_indy_entries_if_non_deterministic() { + ConstantPool* cp = constant_pool(); + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(cp); + for (int i = 0; i < _resolved_indy_entries->length(); i++) { + ResolvedIndyEntry* rei = _resolved_indy_entries->adr_at(i); + int cp_index = rei->constant_pool_index(); + bool archived = false; + bool resolved = rei->is_resolved(); + if (resolved && AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index)) { + rei->mark_and_relocate(); + archived = true; + } else { + rei->remove_unshareable_info(); + } + if (resolved) { + LogStreamHandle(Trace, cds, resolve) log; + if (log.is_enabled()) { + ResourceMark rm; + int bsm = cp->bootstrap_method_ref_index_at(cp_index); + int bsm_ref = cp->method_handle_index_at(bsm); + Symbol* bsm_name = cp->uncached_name_ref_at(bsm_ref); + Symbol* bsm_signature = cp->uncached_signature_ref_at(bsm_ref); + Symbol* bsm_klass = cp->klass_name_at(cp->uncached_klass_ref_index_at(bsm_ref)); + log.print("%s indy CP entry [%3d]: %s (%d)", + (archived ? "archived" : "reverted"), + cp_index, cp->pool_holder()->name()->as_C_string(), i); + log.print(" %s %s.%s:%s", (archived ? "=>" : " "), bsm_klass->as_C_string(), + bsm_name->as_C_string(), bsm_signature->as_C_string()); + } + ArchiveBuilder::alloc_stats()->record_indy_cp_entry(archived, resolved && !archived); + } + } +} + +bool ConstantPoolCache::can_archive_resolved_method(ConstantPool* src_cp, ResolvedMethodEntry* method_entry) { InstanceKlass* pool_holder = constant_pool()->pool_holder(); if (!(pool_holder->is_shared_boot_class() || pool_holder->is_shared_platform_class() || pool_holder->is_shared_app_class())) { @@ -520,10 +551,9 @@ bool ConstantPoolCache::can_archive_resolved_method(ResolvedMethodEntry* method_ } int cp_index = method_entry->constant_pool_index(); - ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(constant_pool()); assert(src_cp->tag_at(cp_index).is_method() || src_cp->tag_at(cp_index).is_interface_method(), "sanity"); - if (!ClassPrelinker::is_resolution_deterministic(src_cp, cp_index)) { + if (!AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index)) { return false; } @@ -531,11 +561,16 @@ bool ConstantPoolCache::can_archive_resolved_method(ResolvedMethodEntry* method_ method_entry->is_resolved(Bytecodes::_invokevirtual) || method_entry->is_resolved(Bytecodes::_invokespecial)) { return true; + } else if (method_entry->is_resolved(Bytecodes::_invokehandle)) { + if (CDSConfig::is_dumping_invokedynamic()) { + // invokehandle depends on archived MethodType and LambdaForms. + return true; + } else { + return false; + } } else { - // invokestatic and invokehandle are not supported yet. return false; } - } #endif // INCLUDE_CDS diff --git a/src/hotspot/share/oops/cpCache.hpp b/src/hotspot/share/oops/cpCache.hpp index f95f141d845..8652409a1a4 100644 --- a/src/hotspot/share/oops/cpCache.hpp +++ b/src/hotspot/share/oops/cpCache.hpp @@ -224,8 +224,9 @@ class ConstantPoolCache: public MetaspaceObj { #if INCLUDE_CDS void remove_resolved_field_entries_if_non_deterministic(); + void remove_resolved_indy_entries_if_non_deterministic(); void remove_resolved_method_entries_if_non_deterministic(); - bool can_archive_resolved_method(ResolvedMethodEntry* method_entry); + bool can_archive_resolved_method(ConstantPool* src_cp, ResolvedMethodEntry* method_entry); #endif // RedefineClasses support diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index a9024bf4a9f..d2761667f64 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" #include "cds/cdsEnumKlass.hpp" @@ -734,6 +735,18 @@ bool InstanceKlass::is_sealed() const { _permitted_subclasses != Universe::the_empty_short_array(); } +// JLS 8.9: An enum class is either implicitly final and derives +// from java.lang.Enum, or else is implicitly sealed to its +// anonymous subclasses. This query detects both kinds. +// It does not validate the finality or +// sealing conditions: it merely checks for a super of Enum. +// This is sufficient for recognizing well-formed enums. +bool InstanceKlass::is_enum_subclass() const { + InstanceKlass* s = java_super(); + return (s == vmClasses::Enum_klass() || + (s != nullptr && s->java_super() == vmClasses::Enum_klass())); +} + bool InstanceKlass::should_be_initialized() const { return !is_initialized(); } @@ -791,6 +804,68 @@ void InstanceKlass::initialize(TRAPS) { } } +#ifdef ASSERT +void InstanceKlass::assert_no_clinit_will_run_for_aot_initialized_class() const { + assert(has_aot_initialized_mirror(), "must be"); + + InstanceKlass* s = java_super(); + if (s != nullptr) { + DEBUG_ONLY(ResourceMark rm); + assert(s->is_initialized(), "super class %s of aot-inited class %s must have been initialized", + s->external_name(), external_name()); + s->assert_no_clinit_will_run_for_aot_initialized_class(); + } + + Array* interfaces = local_interfaces(); + int len = interfaces->length(); + for (int i = 0; i < len; i++) { + InstanceKlass* intf = interfaces->at(i); + if (!intf->is_initialized()) { + ResourceMark rm; + // Note: an interface needs to be marked as is_initialized() only if + // - it has a + // - it has declared a default method. + assert(!intf->interface_needs_clinit_execution_as_super(/*also_check_supers*/false), + "uninitialized super interface %s of aot-inited class %s must not have ", + intf->external_name(), external_name()); + } + } +} +#endif + +#if INCLUDE_CDS +void InstanceKlass::initialize_with_aot_initialized_mirror(TRAPS) { + assert(has_aot_initialized_mirror(), "must be"); + assert(CDSConfig::is_loading_heap(), "must be"); + assert(CDSConfig::is_using_aot_linked_classes(), "must be"); + assert_no_clinit_will_run_for_aot_initialized_class(); + + if (is_initialized()) { + return; + } + + if (log_is_enabled(Info, cds, init)) { + ResourceMark rm; + log_info(cds, init)("%s (aot-inited)", external_name()); + } + + link_class(CHECK); + +#ifdef ASSERT + { + Handle h_init_lock(THREAD, init_lock()); + ObjectLocker ol(h_init_lock, THREAD); + assert(!is_initialized(), "sanity"); + assert(!is_being_initialized(), "sanity"); + assert(!is_in_error_state(), "sanity"); + } +#endif + + set_init_thread(THREAD); + AOTClassInitializer::call_runtime_setup(THREAD, this); + set_initialization_state_and_notify(fully_initialized, CHECK); +} +#endif bool InstanceKlass::verify_code(TRAPS) { // 1) Verify the bytecodes @@ -1578,7 +1653,10 @@ void InstanceKlass::call_class_initializer(TRAPS) { #if INCLUDE_CDS // This is needed to ensure the consistency of the archived heap objects. - if (has_archived_enum_objs()) { + if (has_aot_initialized_mirror() && CDSConfig::is_loading_heap()) { + AOTClassInitializer::call_runtime_setup(THREAD, this); + return; + } else if (has_archived_enum_objs()) { assert(is_shared(), "must be"); bool initialized = CDSEnumKlass::initialize_enum_klass(this, CHECK); if (initialized) { @@ -1607,6 +1685,47 @@ void InstanceKlass::call_class_initializer(TRAPS) { } } +// If a class that implements this interface is initialized, is the JVM required +// to first execute a method declared in this interface, +// or (if also_check_supers==true) any of the super types of this interface? +// +// JVMS 5.5. Initialization, step 7: Next, if C is a class rather than +// an interface, then let SC be its superclass and let SI1, ..., SIn +// be all superinterfaces of C (whether direct or indirect) that +// declare at least one non-abstract, non-static method. +// +// So when an interface is initialized, it does not look at its +// supers. But a proper class will ensure that all of its supers have +// run their methods, except that it disregards interfaces +// that lack a non-static concrete method (i.e., a default method). +// Therefore, you should probably call this method only when the +// current class is a super of some proper class, not an interface. +bool InstanceKlass::interface_needs_clinit_execution_as_super(bool also_check_supers) const { + assert(is_interface(), "must be"); + + if (!has_nonstatic_concrete_methods()) { + // quick check: no nonstatic concrete methods are declared by this or any super interfaces + return false; + } + + // JVMS 5.5. Initialization + // ...If C is an interface that declares a non-abstract, + // non-static method, the initialization of a class that + // implements C directly or indirectly. + if (declares_nonstatic_concrete_methods() && class_initializer() != nullptr) { + return true; + } + if (also_check_supers) { + Array* all_ifs = transitive_interfaces(); + for (int i = 0; i < all_ifs->length(); ++i) { + InstanceKlass* super_intf = all_ifs->at(i); + if (super_intf->declares_nonstatic_concrete_methods() && super_intf->class_initializer() != nullptr) { + return true; + } + } + } + return false; +} void InstanceKlass::mask_for(const methodHandle& method, int bci, InterpreterOopMap* entry_for) { @@ -2497,6 +2616,7 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) { } } + it->push(&_nest_host); it->push(&_nest_members); it->push(&_permitted_subclasses); it->push(&_record_components); @@ -2560,8 +2680,12 @@ void InstanceKlass::remove_unshareable_info() { _methods_jmethod_ids = nullptr; _jni_ids = nullptr; _oop_map_cache = nullptr; - // clear _nest_host to ensure re-load at runtime - _nest_host = nullptr; + if (CDSConfig::is_dumping_invokedynamic() && HeapShared::is_lambda_proxy_klass(this)) { + // keep _nest_host + } else { + // clear _nest_host to ensure re-load at runtime + _nest_host = nullptr; + } init_shared_package_entry(); _dep_context_last_cleaned = 0; @@ -2700,6 +2824,18 @@ bool InstanceKlass::can_be_verified_at_dumptime() const { } return true; } + +int InstanceKlass::shared_class_loader_type() const { + if (is_shared_boot_class()) { + return ClassLoader::BOOT_LOADER; + } else if (is_shared_platform_class()) { + return ClassLoader::PLATFORM_LOADER; + } else if (is_shared_app_class()) { + return ClassLoader::APP_LOADER; + } else { + return ClassLoader::OTHER; + } +} #endif // INCLUDE_CDS #if INCLUDE_JVMTI @@ -2907,6 +3043,10 @@ ModuleEntry* InstanceKlass::module() const { return class_loader_data()->unnamed_module(); } +bool InstanceKlass::in_javabase_module() const { + return module()->name() == vmSymbols::java_base(); +} + void InstanceKlass::set_package(ClassLoaderData* loader_data, PackageEntry* pkg_entry, TRAPS) { // ensure java/ packages only loaded by boot or platform builtin loaders diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index daaa16f2059..b3283a04d44 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -323,6 +323,7 @@ class InstanceKlass: public Klass { void set_shared_loading_failed() { _misc_flags.set_shared_loading_failed(true); } #if INCLUDE_CDS + int shared_class_loader_type() const; void set_shared_class_loader_type(s2 loader_type) { _misc_flags.set_shared_class_loader_type(loader_type); } void assign_class_loader_type() { _misc_flags.assign_class_loader_type(_class_loader_data); } #endif @@ -429,6 +430,9 @@ class InstanceKlass: public Klass { } bool is_record() const; + // test for enum class (or possibly an anonymous subclass within a sealed enum) + bool is_enum_subclass() const; + // permitted subclasses Array* permitted_subclasses() const { return _permitted_subclasses; } void set_permitted_subclasses(Array* s) { _permitted_subclasses = s; } @@ -475,6 +479,7 @@ public: // package PackageEntry* package() const { return _package_entry; } ModuleEntry* module() const; + bool in_javabase_module() const; bool in_unnamed_package() const { return (_package_entry == nullptr); } void set_package(ClassLoaderData* loader_data, PackageEntry* pkg_entry, TRAPS); // If the package for the InstanceKlass is in the boot loader's package entry @@ -531,12 +536,15 @@ public: // initialization (virtuals from Klass) bool should_be_initialized() const; // means that initialize should be called + void initialize_with_aot_initialized_mirror(TRAPS); + void assert_no_clinit_will_run_for_aot_initialized_class() const NOT_DEBUG_RETURN; void initialize(TRAPS); void link_class(TRAPS); bool link_class_or_fail(TRAPS); // returns false on failure void rewrite_class(TRAPS); void link_methods(TRAPS); Method* class_initializer() const; + bool interface_needs_clinit_execution_as_super(bool also_check_supers=true) const; // reference type ReferenceType reference_type() const { return (ReferenceType)_reference_type; } diff --git a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp index 2629be64bea..8c128ab9ce6 100644 --- a/src/hotspot/share/oops/klass.hpp +++ b/src/hotspot/share/oops/klass.hpp @@ -195,7 +195,12 @@ private: _has_archived_enum_objs = 1 << 4, // This class was not loaded from a classfile in the module image // or classpath. - _is_generated_shared_class = 1 << 5 + _is_generated_shared_class = 1 << 5, + // archived mirror already initialized by AOT-cache assembly: no further need to call + _has_aot_initialized_mirror = 1 << 6, + // If this class has been aot-inititalized, do we need to call its runtimeSetup() + // method during the production run? + _is_runtime_setup_required = 1 << 7, }; #endif @@ -377,6 +382,23 @@ protected: NOT_CDS(return false;) } + void set_has_aot_initialized_mirror() { + CDS_ONLY(_shared_class_flags |= _has_aot_initialized_mirror;) + } + bool has_aot_initialized_mirror() const { + CDS_ONLY(return (_shared_class_flags & _has_aot_initialized_mirror) != 0;) + NOT_CDS(return false;) + } + + void set_is_runtime_setup_required() { + assert(has_aot_initialized_mirror(), "sanity"); + CDS_ONLY(_shared_class_flags |= _is_runtime_setup_required;) + } + bool is_runtime_setup_required() const { + CDS_ONLY(return (_shared_class_flags & _is_runtime_setup_required) != 0;) + NOT_CDS(return false;) + } + bool is_shared() const { // shadows MetaspaceObj::is_shared)() CDS_ONLY(return (_shared_class_flags & _is_shared_class) != 0;) NOT_CDS(return false;) diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index 26a047449ed..917c93304c1 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -1432,6 +1432,7 @@ methodHandle Method::make_method_handle_intrinsic(vmIntrinsics::ID iid, cp->symbol_at_put(_imcp_invoke_name, name); cp->symbol_at_put(_imcp_invoke_signature, signature); cp->set_has_preresolution(); + cp->set_is_for_method_handle_intrinsic(); // decide on access bits: public or not? int flags_bits = (JVM_ACC_NATIVE | JVM_ACC_SYNTHETIC | JVM_ACC_FINAL); @@ -1480,6 +1481,16 @@ methodHandle Method::make_method_handle_intrinsic(vmIntrinsics::ID iid, return m; } +#if INCLUDE_CDS +void Method::restore_archived_method_handle_intrinsic(methodHandle m, TRAPS) { + m->link_method(m, CHECK); + + if (m->intrinsic_id() == vmIntrinsics::_linkToNative) { + m->set_interpreter_entry(m->adapter()->get_i2c_entry()); + } +} +#endif + Klass* Method::check_non_bcp_klass(Klass* klass) { if (klass != nullptr && klass->class_loader() != nullptr) { if (klass->is_objArray_klass()) diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 232e5fb577e..cc3caccd16a 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -122,6 +122,7 @@ class Method : public Metadata { #if INCLUDE_CDS void remove_unshareable_info(); void restore_unshareable_info(TRAPS); + static void restore_archived_method_handle_intrinsic(methodHandle m, TRAPS); #endif // accessors for instance variables diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 12ed10630fc..3288f2eefe4 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -2838,7 +2838,7 @@ static void thread_entry(JavaThread* thread, TRAPS) { JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) #if INCLUDE_CDS - if (CDSConfig::is_dumping_static_archive()) { + if (CDSConfig::allow_only_single_java_thread()) { // During java -Xshare:dump, if we allow multiple Java threads to // execute in parallel, symbols and classes may be loaded in // random orders which will make the resulting CDS archive diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 087f426c537..c68692a2fa1 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1075,6 +1075,16 @@ bool JvmtiExport::has_early_class_hook_env() { return false; } +bool JvmtiExport::has_early_vmstart_env() { + JvmtiEnvIterator it; + for (JvmtiEnv* env = it.first(); env != nullptr; env = it.next(env)) { + if (env->early_vmstart_env()) { + return true; + } + } + return false; +} + bool JvmtiExport::_should_post_class_file_load_hook = false; // This flag is read by C2 during VM internal objects allocation diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp index e0fb84f2c03..324e437411a 100644 --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -371,6 +371,7 @@ class JvmtiExport : public AllStatic { } static bool is_early_phase() NOT_JVMTI_RETURN_(false); static bool has_early_class_hook_env() NOT_JVMTI_RETURN_(false); + static bool has_early_vmstart_env() NOT_JVMTI_RETURN_(false); // Return true if the class was modified by the hook. static bool post_class_file_load_hook(Symbol* h_name, Handle class_loader, Handle h_protection_domain, diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 7b8604659a0..175a85e33fb 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -2534,19 +2534,23 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m // -Xshare:dump } else if (match_option(option, "-Xshare:dump")) { CDSConfig::enable_dumping_static_archive(); + CDSConfig::set_old_cds_flags_used(); // -Xshare:on } else if (match_option(option, "-Xshare:on")) { UseSharedSpaces = true; RequireSharedSpaces = true; + CDSConfig::set_old_cds_flags_used(); // -Xshare:auto || -XX:ArchiveClassesAtExit= } else if (match_option(option, "-Xshare:auto")) { UseSharedSpaces = true; RequireSharedSpaces = false; xshare_auto_cmd_line = true; + CDSConfig::set_old_cds_flags_used(); // -Xshare:off } else if (match_option(option, "-Xshare:off")) { UseSharedSpaces = false; RequireSharedSpaces = false; + CDSConfig::set_old_cds_flags_used(); // -Xverify } else if (match_option(option, "-Xverify", &tail)) { if (strcmp(tail, ":all") == 0 || strcmp(tail, "") == 0) { diff --git a/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp b/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp index 8d9cbe33d12..843ee24c909 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, 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 @@ -310,6 +310,17 @@ JVMFlag::Error JVMFlagAccess::set_impl(JVMFlag* flag, void* value, JVMFlagOrigin JVMFlag::Error JVMFlagAccess::set_ccstr(JVMFlag* flag, ccstr* value, JVMFlagOrigin origin) { if (flag == nullptr) return JVMFlag::INVALID_FLAG; if (!flag->is_ccstr()) return JVMFlag::WRONG_FORMAT; + const JVMTypedFlagLimit* constraint = (const JVMTypedFlagLimit*)JVMFlagLimit::get_constraint(flag); + if (constraint != nullptr && constraint->phase() <= JVMFlagLimit::validating_phase()) { + bool verbose = JVMFlagLimit::verbose_checks_needed() | (origin == JVMFlagOrigin::ERGONOMIC); + JVMFlag::Error err = ((JVMFlagConstraintFunc_ccstr)constraint->constraint_func())(*value, verbose); + if (err != JVMFlag::SUCCESS) { + if (origin == JVMFlagOrigin::ERGONOMIC) { + fatal("FLAG_SET_ERGO cannot be used to set an invalid value for %s", flag->name()); + } + return err; + } + } ccstr old_value = flag->get_ccstr(); trace_flag_changed(flag, old_value, *value, origin); char* new_value = nullptr; diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp index 1c02e929113..354e828372f 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -32,6 +32,21 @@ #include "runtime/task.hpp" #include "utilities/powerOfTwo.hpp" +JVMFlag::Error AOTModeConstraintFunc(ccstr value, bool verbose) { + if (strcmp(value, "off") != 0 && + strcmp(value, "record") != 0 && + strcmp(value, "create") != 0 && + strcmp(value, "auto") != 0 && + strcmp(value, "on") != 0) { + JVMFlag::printError(verbose, + "Unrecognized value %s for AOTMode. Must be one of the following: " + "off, record, create, auto, on\n", + value); + return JVMFlag::VIOLATES_CONSTRAINT; + } + + return JVMFlag::SUCCESS; +} JVMFlag::Error ObjectAlignmentInBytesConstraintFunc(int value, bool verbose) { if (!is_power_of_2(value)) { JVMFlag::printError(verbose, diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp index cbe28456b8a..20d420a8e7e 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -34,6 +34,7 @@ */ #define RUNTIME_CONSTRAINTS(f) \ + f(ccstr, AOTModeConstraintFunc) \ f(int, ObjectAlignmentInBytesConstraintFunc) \ f(int, ContendedPaddingWidthConstraintFunc) \ f(int, PerfDataSamplingIntervalFunc) \ diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 29386a0c368..41ac7bc6627 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -24,12 +24,15 @@ */ #include "precompiled.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" #include "cds/metaspaceShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaThreadStatus.hpp" +#include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" @@ -350,12 +353,15 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_System(), CHECK); // The VM creates & returns objects of this class. Make sure it's initialized. initialize_class(vmSymbols::java_lang_Class(), CHECK); + initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK); Handle thread_group = create_initial_thread_group(CHECK); Universe::set_main_thread_group(thread_group()); initialize_class(vmSymbols::java_lang_Thread(), CHECK); create_initial_thread(thread_group, main_thread, CHECK); + HeapShared::init_box_classes(CHECK); + // The VM creates objects of this class. initialize_class(vmSymbols::java_lang_Module(), CHECK); @@ -403,6 +409,7 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK); initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK); initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK); + initialize_class(vmSymbols::java_lang_InternalError(), CHECK); } void Threads::initialize_jsr292_core_classes(TRAPS) { @@ -412,6 +419,10 @@ void Threads::initialize_jsr292_core_classes(TRAPS) { initialize_class(vmSymbols::java_lang_invoke_ResolvedMethodName(), CHECK); initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK); initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK); + + if (UseSharedSpaces) { + HeapShared::initialize_java_lang_invoke(CHECK); + } } jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { @@ -737,6 +748,11 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { } #endif + if (CDSConfig::is_using_aot_linked_classes()) { + AOTLinkedClassBulkLoader::finish_loading_javabase_classes(CHECK_JNI_ERR); + SystemDictionary::restore_archived_method_handle_intrinsics(); + } + // Start string deduplication thread if requested. if (StringDedup::is_enabled()) { StringDedup::start(); @@ -752,6 +768,13 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { // loaded until phase 2 completes call_initPhase2(CHECK_JNI_ERR); + if (CDSConfig::is_using_aot_linked_classes()) { + AOTLinkedClassBulkLoader::load_non_javabase_classes(THREAD); + } +#ifndef PRODUCT + HeapShared::initialize_test_class_from_archive(THREAD); +#endif + JFR_ONLY(Jfr::on_create_vm_2();) // Always call even when there are not JVMTI environments yet, since environments diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index ba63f2d538f..771084384c8 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -225,6 +225,11 @@ public final class Class implements java.io.Serializable, private static native void registerNatives(); static { + runtimeSetup(); + } + + // Called from JVM when loading an AOT cache + private static void runtimeSetup() { registerNatives(); } @@ -3425,6 +3430,15 @@ public final class Class implements java.io.Serializable, } private static ReflectionFactory reflectionFactory; + /** + * When CDS is enabled, the Class class may be aot-initialized. However, + * we can't archive reflectionFactory, so we reset it to null, so it + * will be allocated again at runtime. + */ + private static void resetArchivedStates() { + reflectionFactory = null; + } + /** * Returns the elements of this enum class or null if this * Class object does not represent an enum class. diff --git a/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java b/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java index 4de0d9c95cb..8d84c5f8096 100644 --- a/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java +++ b/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, 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 @@ -38,6 +38,7 @@ import static java.lang.invoke.LambdaForm.BasicType.*; import static java.lang.invoke.MethodHandleImpl.Intrinsic; import static java.lang.invoke.MethodHandleImpl.NF_loop; import static java.lang.invoke.MethodHandleImpl.makeIntrinsic; +import static java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE; /** Transforms on LFs. * A lambda-form editor can derive new LFs from its base LF. @@ -89,12 +90,17 @@ class LambdaFormEditor { * Tightly coupled with the TransformKey class, which is used to lookup existing * Transforms. */ - private static final class Transform extends SoftReference { + private static final class Transform { + final Object cache; final long packedBytes; final byte[] fullBytes; private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) { - super(result); + if (USE_SOFT_CACHE) { + cache = new SoftReference(result); + } else { + cache = result; + } this.packedBytes = packedBytes; this.fullBytes = fullBytes; } @@ -135,6 +141,15 @@ class LambdaFormEditor { } return buf.toString(); } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public LambdaForm get() { + if (cache instanceof LambdaForm lf) { + return lf; + } else { + return ((SoftReference)cache).get(); + } + } } /** diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java index ac9765ddc3b..4ffbf826317 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, 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,9 +28,11 @@ package java.lang.invoke; import jdk.internal.misc.VM; import jdk.internal.ref.CleanerFactory; import sun.invoke.util.Wrapper; +import sun.security.action.GetPropertyAction; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Field; +import java.util.Properties; import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.TRACE_METHOD_LINKAGE; @@ -690,4 +692,23 @@ class MethodHandleNatives { return (definingClass.isAssignableFrom(symbolicRefClass) || // Msym overrides Mdef symbolicRefClass.isInterface()); // Mdef implements Msym } + + //--- AOTCache support + + /** + * In normal execution, this is set to true, so that LambdaFormEditor and MethodTypeForm will + * use soft references to allow class unloading. + * + * When dumping the AOTCache, this is set to false so that no cached heap objects will + * contain soft references (which are not yet supported by AOTCache - see JDK-8341587). AOTCache + * only stores LambdaFormEditors and MethodTypeForms for classes in the boot/platform/app loaders. + * Such classes will never be unloaded, so it's OK to use hard references. + */ + static final boolean USE_SOFT_CACHE; + + static { + Properties props = GetPropertyAction.privilegedGetProperties(); + USE_SOFT_CACHE = Boolean.parseBoolean( + props.getProperty("java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE", "true")); + } } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodType.java b/src/java.base/share/classes/java/lang/invoke/MethodType.java index 2b3843b09e3..5e1c0f8581c 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodType.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodType.java @@ -31,6 +31,8 @@ import java.lang.constant.MethodTypeDesc; import java.util.Arrays; import java.util.Collections; import java.util.function.Supplier; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -40,6 +42,7 @@ import java.util.concurrent.ConcurrentHashMap; import jdk.internal.util.ReferencedKeySet; import jdk.internal.util.ReferenceKey; +import jdk.internal.misc.CDS; import jdk.internal.vm.annotation.Stable; import sun.invoke.util.BytecodeDescriptor; import sun.invoke.util.VerifyType; @@ -391,6 +394,17 @@ class MethodType ptypes = NO_PTYPES; trusted = true; } MethodType primordialMT = new MethodType(rtype, ptypes); + if (archivedMethodTypes != null) { + // If this JVM process reads from archivedMethodTypes, it never + // modifies the table. So there's no need for synchronization. + // See copyInternTable() below. + assert CDS.isUsingArchive(); + MethodType mt = archivedMethodTypes.get(primordialMT); + if (mt != null) { + return mt; + } + } + MethodType mt = internTable.get(primordialMT); if (mt != null) return mt; @@ -409,7 +423,9 @@ class MethodType mt.form = MethodTypeForm.findForm(mt); return internTable.intern(mt); } + private static final @Stable MethodType[] objectOnlyTypes = new MethodType[20]; + private static @Stable HashMap archivedMethodTypes; /** * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array. @@ -1380,4 +1396,30 @@ s.writeObject(this.parameterArray()); wrapAlt = null; return mt; } + + static HashMap copyInternTable() { + HashMap copy = new HashMap<>(); + + for (Iterator i = internTable.iterator(); i.hasNext(); ) { + MethodType t = i.next(); + copy.put(t, t); + } + + return copy; + } + + // This is called from C code, at the very end of Java code execution + // during the AOT cache assembly phase. + static void createArchivedObjects() { + // After the archivedMethodTypes field is assigned, this table + // is never modified. So we don't need synchronization when reading from + // it (which happens only in a future JVM process, never in the current process). + // + // @implNote CDS.isDumpingStaticArchive() is mutually exclusive with + // CDS.isUsingArchive(); at most one of them can return true for any given JVM + // process. + assert CDS.isDumpingStaticArchive(); + archivedMethodTypes = copyInternTable(); + internTable.clear(); + } } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java b/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java index 09377618797..69545478ec4 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, 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,6 +30,7 @@ import sun.invoke.util.Wrapper; import java.lang.ref.SoftReference; import static java.lang.invoke.MethodHandleStatics.newIllegalArgumentException; +import static java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE; /** * Shared information for a group of method types, which differ @@ -51,7 +52,7 @@ final class MethodTypeForm { final MethodType basicType; // the canonical erasure, with primitives simplified // Cached adapter information: - final SoftReference[] methodHandles; + private final Object[] methodHandles; // Indexes into methodHandles: static final int @@ -61,7 +62,7 @@ final class MethodTypeForm { MH_LIMIT = 3; // Cached lambda form information, for basic types only: - final SoftReference[] lambdaForms; + private final Object[] lambdaForms; // Indexes into lambdaForms: static final int @@ -109,39 +110,55 @@ final class MethodTypeForm { return basicType; } + @SuppressWarnings({"rawtypes", "unchecked"}) public MethodHandle cachedMethodHandle(int which) { - SoftReference entry = methodHandles[which]; - return (entry != null) ? entry.get() : null; + Object entry = methodHandles[which]; + if (entry == null) { + return null; + } else if (entry instanceof MethodHandle mh) { + return mh; + } else { + return ((SoftReference)entry).get(); + } } public synchronized MethodHandle setCachedMethodHandle(int which, MethodHandle mh) { // Simulate a CAS, to avoid racy duplication of results. - SoftReference entry = methodHandles[which]; - if (entry != null) { - MethodHandle prev = entry.get(); - if (prev != null) { - return prev; - } + MethodHandle prev = cachedMethodHandle(which); + if (prev != null) { + return prev; + } + if (USE_SOFT_CACHE) { + methodHandles[which] = new SoftReference<>(mh); + } else { + methodHandles[which] = mh; } - methodHandles[which] = new SoftReference<>(mh); return mh; } + @SuppressWarnings({"rawtypes", "unchecked"}) public LambdaForm cachedLambdaForm(int which) { - SoftReference entry = lambdaForms[which]; - return (entry != null) ? entry.get() : null; + Object entry = lambdaForms[which]; + if (entry == null) { + return null; + } else if (entry instanceof LambdaForm lf) { + return lf; + } else { + return ((SoftReference)entry).get(); + } } public synchronized LambdaForm setCachedLambdaForm(int which, LambdaForm form) { // Simulate a CAS, to avoid racy duplication of results. - SoftReference entry = lambdaForms[which]; - if (entry != null) { - LambdaForm prev = entry.get(); - if (prev != null) { - return prev; - } + LambdaForm prev = cachedLambdaForm(which); + if (prev != null) { + return prev; + } + if (USE_SOFT_CACHE) { + lambdaForms[which] = new SoftReference<>(form); + } else { + lambdaForms[which] = form; } - lambdaForms[which] = new SoftReference<>(form); return form; } @@ -191,8 +208,8 @@ final class MethodTypeForm { this.primitiveCount = primitiveCount; this.parameterSlotCount = (short)pslotCount; - this.lambdaForms = new SoftReference[LF_LIMIT]; - this.methodHandles = new SoftReference[MH_LIMIT]; + this.lambdaForms = new Object[LF_LIMIT]; + this.methodHandles = new Object[MH_LIMIT]; } else { this.basicType = MethodType.methodType(basicReturnType, basicPtypes, true); // fill in rest of data from the basic type: diff --git a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java index 2f5b8f2407e..7e91057cbf4 100644 --- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java +++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java @@ -1080,6 +1080,8 @@ public final class StringConcatFactory { * without copying. */ private static final class InlineHiddenClassStrategy { + // The CLASS_NAME prefix must be the same as used by HeapShared::is_string_concat_klass() + // in the HotSpot code. static final String CLASS_NAME = "java.lang.String$$StringConcat"; static final String METHOD_NAME = "concat"; diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java index 5a23fdb05a6..53919011c6f 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java @@ -70,6 +70,7 @@ import java.util.function.ToLongFunction; import java.util.stream.Stream; import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; +import jdk.internal.vm.annotation.Stable; /** * A hash table supporting full concurrency of retrievals and @@ -595,7 +596,16 @@ public class ConcurrentHashMap extends AbstractMap static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash /** Number of CPUS, to place bounds on some sizings */ - static final int NCPU = Runtime.getRuntime().availableProcessors(); + static @Stable int NCPU; + + static { + runtimeSetup(); + } + + // Called from JVM when loading an AOT cache. + private static void runtimeSetup() { + NCPU = Runtime.getRuntime().availableProcessors(); + } /** * Serialized pseudo-fields, provided only for jdk7 compatibility. diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java index ec6b7591282..e69690f876f 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -57,6 +57,11 @@ public final class Unsafe { private static native void registerNatives(); static { + runtimeSetup(); + } + + // Called from JVM when loading an AOT cache + private static void runtimeSetup() { registerNatives(); } diff --git a/test/hotspot/jtreg/ProblemList-AotJdk.txt b/test/hotspot/jtreg/ProblemList-AotJdk.txt new file mode 100644 index 00000000000..2528f8d377e --- /dev/null +++ b/test/hotspot/jtreg/ProblemList-AotJdk.txt @@ -0,0 +1,18 @@ +runtime/modules/PatchModule/PatchModuleClassList.java 0000000 generic-all +runtime/NMT/NMTWithCDS.java 0000000 generic-all +runtime/symbols/TestSharedArchiveConfigFile.java 0000000 generic-all + +gc/arguments/TestSerialHeapSizeFlags.java 0000000 generic-all +gc/TestAllocateHeapAtMultiple.java 0000000 generic-all +gc/TestAllocateHeapAt.java 0000000 generic-all + +# use -Xshare +serviceability/sa/ClhsdbCDSJstackPrintAll.java 0000000 generic-all +serviceability/sa/ClhsdbCDSCore.java 0000000 generic-all +serviceability/sa/CDSJMapClstats.java 0000000 generic-all +compiler/intrinsics/klass/TestIsPrimitive.java 0000000 generic-all + +# This test is incompatible with AOTClassLinking. +# It has the assumption about unresolved Integer. +# However when AOTClassLinking is enabled, Integer is always resolved at JVM start-up. +compiler/ciReplay/TestInliningProtectionDomain.java 0000000 generic-all diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index af97ff465de..1e5094bd116 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -74,6 +74,7 @@ requires.properties= \ vm.rtm.compiler \ vm.cds \ vm.cds.custom.loaders \ + vm.cds.supports.aot.class.linking \ vm.cds.write.archived.java.heap \ vm.continuations \ vm.jvmti \ diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 575c0caa674..718ecf610f1 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -56,6 +56,7 @@ hotspot_runtime_no_cds = \ hotspot_runtime_non_cds_mode = \ runtime \ -runtime/cds/CheckSharingWithDefaultArchive.java \ + -runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java \ -runtime/cds/appcds/dynamicArchive/DynamicSharedSymbols.java \ -runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java \ -runtime/cds/appcds/jcmd @@ -430,6 +431,7 @@ hotspot_cds_only = \ hotspot_appcds_dynamic = \ runtime/cds/appcds/ \ + -runtime/cds/appcds/applications \ -runtime/cds/appcds/cacheObject \ -runtime/cds/appcds/complexURI \ -runtime/cds/appcds/customLoader \ @@ -447,10 +449,12 @@ hotspot_appcds_dynamic = \ -runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java \ -runtime/cds/appcds/lambdaForm/DefaultClassListLFInvokers.java \ -runtime/cds/appcds/methodHandles \ + -runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java \ -runtime/cds/appcds/sharedStrings \ -runtime/cds/appcds/resolvedConstants \ -runtime/cds/appcds/ArchiveRelocationTest.java \ -runtime/cds/appcds/BadBSM.java \ + -runtime/cds/appcds/CommandLineFlagCombo.java \ -runtime/cds/appcds/DumpClassList.java \ -runtime/cds/appcds/DumpClassListWithLF.java \ -runtime/cds/appcds/DumpRuntimeClassesTest.java \ @@ -518,6 +522,61 @@ hotspot_cds_epsilongc = \ runtime/cds/appcds/jigsaw \ runtime/cds/appcds/loaderConstraints +# Run CDS tests with -XX:+AOTClassLinking. This should include most CDS tests, except for +# those that rely on redefining classes that are already archived. +hotspot_aot_classlinking = \ + runtime/cds \ + -runtime/cds/appcds/aotClassLinking \ + -runtime/cds/appcds/BadBSM.java \ + -runtime/cds/appcds/cacheObject/ArchivedIntegerCacheTest.java \ + -runtime/cds/appcds/cacheObject/ArchivedModuleCompareTest.java \ + -runtime/cds/appcds/CDSandJFR.java \ + -runtime/cds/appcds/customLoader/HelloCustom_JFR.java \ + -runtime/cds/appcds/customLoader/ParallelTestMultiFP.java \ + -runtime/cds/appcds/customLoader/ParallelTestSingleFP.java \ + -runtime/cds/appcds/customLoader/SameNameInTwoLoadersTest.java \ + -runtime/cds/appcds/DumpClassListWithLF.java \ + -runtime/cds/appcds/dynamicArchive/ModulePath.java \ + -runtime/cds/appcds/dynamicArchive/LambdaInBaseArchive.java \ + -runtime/cds/appcds/dynamicArchive/LambdasInTwoArchives.java \ + -runtime/cds/appcds/HelloExtTest.java \ + -runtime/cds/appcds/javaldr/AnonVmClassesDuringDump.java \ + -runtime/cds/appcds/javaldr/GCDuringDump.java \ + -runtime/cds/appcds/javaldr/LockDuringDump.java \ + -runtime/cds/appcds/jigsaw/classpathtests/EmptyClassInBootClassPath.java \ + -runtime/cds/appcds/jigsaw/JigsawOptionsCombo.java \ + -runtime/cds/appcds/jigsaw/modulepath/AddOpens.java \ + -runtime/cds/appcds/jigsaw/modulepath/AddModules.java \ + -runtime/cds/appcds/jigsaw/modulepath/JvmtiAddPath.java \ + -runtime/cds/appcds/jigsaw/modulepath/MainModuleOnly.java \ + -runtime/cds/appcds/jigsaw/modulepath/ModulePathAndCP.java \ + -runtime/cds/appcds/jigsaw/modulepath/ModulePathAndCP_JFR.java \ + -runtime/cds/appcds/jigsaw/modulepath/ModulePathAndFMG.java \ + -runtime/cds/appcds/jigsaw/modulepath/OptimizeModuleHandlingTest.java \ + -runtime/cds/appcds/jigsaw/overridetests/OverrideTests.java \ + -runtime/cds/appcds/jigsaw/RedefineClassesInModuleGraph.java \ + -runtime/cds/appcds/JvmtiAddPath.java \ + -runtime/cds/appcds/jvmti \ + -runtime/cds/appcds/LambdaProxyClasslist.java \ + -runtime/cds/appcds/loaderConstraints/LoaderConstraintsTest.java \ + -runtime/cds/appcds/redefineClass \ + -runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java \ + -runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java \ + -runtime/cds/appcds/resolvedConstants/ResolvedConstants.java \ + -runtime/cds/appcds/RewriteBytecodesTest.java \ + -runtime/cds/appcds/SpecifySysLoaderProp.java \ + -runtime/cds/appcds/StaticArchiveWithLambda.java \ + -runtime/cds/appcds/TestEpsilonGCWithCDS.java \ + -runtime/cds/appcds/TestParallelGCWithCDS.java \ + -runtime/cds/appcds/TestSerialGCWithCDS.java \ + -runtime/cds/appcds/TestZGCWithCDS.java \ + -runtime/cds/appcds/TestWithProfiler.java \ + -runtime/cds/serviceability/ReplaceCriticalClassesForSubgraphs.java \ + -runtime/cds/serviceability/ReplaceCriticalClasses.java \ + -runtime/cds/serviceability/transformRelatedClasses/TransformInterfaceAndImplementor.java \ + -runtime/cds/serviceability/transformRelatedClasses/TransformSuperAndSubClasses.java \ + -runtime/cds/serviceability/transformRelatedClasses/TransformSuperSubTwoPckgs.java + # needs -nativepath:/images/test/hotspot/jtreg/native/ hotspot_metaspace = \ gtest/MetaspaceGtests.java \ @@ -537,6 +596,10 @@ tier1_runtime_appcds_exclude = \ runtime/cds/appcds/ \ -:tier1_runtime_appcds +tier1_runtime_no_cds = \ + :tier1_runtime \ + -runtime/cds + # This group should be executed with "jtreg -Dtest.cds.run.with.jfr=true ..." # to test interaction between AppCDS and JFR. It also has the side effect of # testing JVMTI ClassFileLoadHook. diff --git a/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java b/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java index a7f31cf1a08..49265680ef2 100644 --- a/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java +++ b/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, 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 @@ -67,8 +67,23 @@ import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.Platform; import jdk.test.lib.process.OutputAnalyzer; +import jtreg.SkippedException; public class SharedBaseAddress { + static final boolean skipUncompressedOopsTests; + static boolean checkSkipUncompressedOopsTests(String prop) { + String opts = System.getProperty(prop); + return opts.contains("+AOTClassLinking") && + opts.matches(".*[+]Use[A-Za-z]+GC.*") && !opts.contains("+UseG1GC"); + } + static { + // AOTClassLinking requires the ability to load archived heap objects. However, + // due to JDK-8341371, only G1GC supports loading archived heap objects + // with uncompressed oops. + skipUncompressedOopsTests = + checkSkipUncompressedOopsTests("test.vm.opts") || + checkSkipUncompressedOopsTests("test.java.opts"); + } // shared base address test table for {32, 64}bit VM private static final String[] testTableShared = { @@ -100,6 +115,10 @@ public class SharedBaseAddress { int end = args[0].equals("0") ? mid : testTable.length; boolean provoke = (args.length > 1 && args[1].equals("provoke")); + if (provoke && skipUncompressedOopsTests) { + throw new SkippedException("Test skipped due to JDK-8341371"); + } + // provoke == true: we want to increase the chance that mapping the generated archive at the designated base // succeeds, to test Klass pointer encoding at that weird location. We do this by sizing heap + class space // small, and by switching off compressed oops. diff --git a/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java new file mode 100644 index 00000000000..3a678eefc5b --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2024, 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 "AOT" aliases for traditional CDS command-line options + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build Hello + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello.jar Hello + * @run driver AOTFlags + */ + +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 AOTFlags { + static String appJar = ClassFileInstaller.getJarPath("hello.jar"); + static String aotConfigFile = "hello.aotconfig"; + static String aotCacheFile = "hello.aot"; + static String helloClass = "Hello"; + + public static void main(String[] args) throws Exception { + positiveTests(); + negativeTests(); + } + + static void positiveTests() throws Exception { + // (1) Training Run + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=record", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + + OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "train"); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + + // (2) Assembly Phase + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-Xlog:cds", + "-cp", appJar); + out = CDSTestUtils.executeAndLog(pb, "asm"); + out.shouldContain("Dumping shared data to file:"); + out.shouldMatch("cds.*hello[.]aot"); + out.shouldHaveExitValue(0); + + // (3) Production Run with AOTCache + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTCache=" + aotCacheFile, + "-Xlog:cds", + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "prod"); + out.shouldContain("Opened archive hello.aot."); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + + // (4) AOTMode=off + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTCache=" + aotCacheFile, + "--show-version", + "-Xlog:cds", + "-XX:AOTMode=off", + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "prod"); + out.shouldNotContain(", sharing"); + out.shouldNotContain("Opened archive hello.aot."); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + + // (5) AOTMode=auto + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTCache=" + aotCacheFile, + "--show-version", + "-Xlog:cds", + "-XX:AOTMode=auto", + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "prod"); + out.shouldContain(", sharing"); + out.shouldContain("Opened archive hello.aot."); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + + // (5) AOTMode=on + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTCache=" + aotCacheFile, + "--show-version", + "-Xlog:cds", + "-XX:AOTMode=on", + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "prod"); + out.shouldContain(", sharing"); + out.shouldContain("Opened archive hello.aot."); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + } + + static void negativeTests() throws Exception { + // (1) Mixing old and new options + String mixOldNewErrSuffix = " cannot be used at the same time with -Xshare:on, -Xshare:auto, " + + "-Xshare:off, -Xshare:dump, DumpLoadedClassList, SharedClassListFile, " + + "or SharedArchiveFile"; + + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xshare:off", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + + OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("Option AOTConfiguration" + mixOldNewErrSuffix); + out.shouldNotHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:SharedArchiveFile=" + aotCacheFile, + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("Option AOTCache" + mixOldNewErrSuffix); + out.shouldNotHaveExitValue(0); + + // (2) Use AOTConfiguration without AOTMode + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("AOTConfiguration can only be used with -XX:AOTMode=record or -XX:AOTMode=create"); + out.shouldNotHaveExitValue(0); + + // (3) Use AOTMode without AOTConfiguration + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=record", + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("-XX:AOTMode=record cannot be used without setting AOTConfiguration"); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=create", + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("-XX:AOTMode=create cannot be used without setting AOTConfiguration"); + out.shouldNotHaveExitValue(0); + + // (4) Bad AOTMode + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=foo", + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("Unrecognized value foo for AOTMode. Must be one of the following: off, record, create, auto, on"); + out.shouldNotHaveExitValue(0); + + // (5) AOTCache specified with -XX:AOTMode=record + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=record", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("AOTCache must not be specified when using -XX:AOTMode=record"); + out.shouldNotHaveExitValue(0); + + // (5) AOTCache not specified with -XX:AOTMode=create + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("AOTCache must be specified when using -XX:AOTMode=create"); + out.shouldNotHaveExitValue(0); + + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/ClassPathAttr.java b/test/hotspot/jtreg/runtime/cds/appcds/ClassPathAttr.java index fe618256f65..df47e6bebc8 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/ClassPathAttr.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/ClassPathAttr.java @@ -190,13 +190,20 @@ public class ClassPathAttr { Files.copy(Paths.get(cp), Paths.get(nonExistPath), StandardCopyOption.REPLACE_EXISTING); - TestCommon.run( + CDSTestUtils.Result result = TestCommon.run( "-Xlog:class+path", "-cp", cp, - "CpAttr6") - .assertNormalExit(output -> { - output.shouldMatch("Archived non-system classes are disabled because the file .*cpattrX.jar exists"); - }); + "CpAttr6"); + if (CDSTestUtils.isAOTClassLinkingEnabled()) { + result.assertAbnormalExit(output -> { + output.shouldMatch("CDS archive has aot-linked classes. It cannot be used because the file .*cpattrX.jar exists"); + }); + + } else { + result.assertNormalExit(output -> { + output.shouldMatch("Archived non-system classes are disabled because the file .*cpattrX.jar exists"); + }); + } } static void testClassPathAttrJarOnCP() throws Exception { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/LambdaContainsOldInf.java b/test/hotspot/jtreg/runtime/cds/appcds/LambdaContainsOldInf.java index 8dec80b9f58..6d1b23628c5 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/LambdaContainsOldInf.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/LambdaContainsOldInf.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -64,7 +64,11 @@ public class LambdaContainsOldInf { OutputAnalyzer output = CDSTestUtils.createArchiveAndCheck(opts); TestCommon.checkExecReturn(output, 0, true, "Skipping OldProvider: Old class has been linked"); - output.shouldMatch("Skipping.LambdaContainsOldInfApp[$][$]Lambda.*0x.*:.*Old.class.has.been.linked"); + if (CDSTestUtils.isAOTClassLinkingEnabled()) { + output.shouldMatch("Cannot aot-resolve Lambda proxy because OldProvider is excluded"); + } else { + output.shouldMatch("Skipping.LambdaContainsOldInfApp[$][$]Lambda.*0x.*:.*Old.class.has.been.linked"); + } // run with archive CDSOptions runOpts = (new CDSOptions()) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithOldClass.java b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithOldClass.java index 3a0bb740387..d7ce22e3cf1 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithOldClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithOldClass.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -68,7 +68,11 @@ public class LambdaWithOldClass { .addSuffix(mainClass); OutputAnalyzer output = CDSTestUtils.runWithArchive(runOpts); output.shouldContain("[class,load] LambdaWithOldClassApp source: shared objects file") - .shouldMatch(".class.load. LambdaWithOldClassApp[$][$]Lambda.*/0x.*source:.*shared objects file") .shouldHaveExitValue(0); + if (!CDSTestUtils.isAOTClassLinkingEnabled()) { + // With AOTClassLinking, we don't archive any lambda with old classes in the method + // signatures. + output.shouldMatch(".class.load. LambdaWithOldClassApp[$][$]Lambda.*/0x.*source:.*shared objects file"); + } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java index 513ec57d279..360fce5879a 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -43,6 +43,11 @@ public class LambdaWithUseImplMethodHandle { // See pkg2/Child.jcod for details about the condition that triggers JDK-8290417 public static void main(String[] args) throws Exception { + test(false); + test(true); + } + + static void test(boolean aotClassLinking) throws Exception { String appJar = ClassFileInstaller.getJarPath("test.jar"); String mainClass = "LambdaWithUseImplMethodHandleApp"; String expectedMsg = "Called BaseWithProtectedMethod::protectedMethod"; @@ -57,6 +62,9 @@ public class LambdaWithUseImplMethodHandle { .addPrefix("-XX:ExtraSharedClassListFile=" + classList, "-cp", appJar) .setArchiveName(archiveName); + if (aotClassLinking) { + opts.addPrefix("-XX:+AOTClassLinking"); + } CDSTestUtils.createArchiveAndCheck(opts); // run with archive diff --git a/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java b/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java index 705df07f841..781e0276b27 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java @@ -148,6 +148,7 @@ public class TestParallelGCWithCDS { } else { String pattern = "((Too small maximum heap)" + "|(GC triggered before VM initialization completed)" + + "|(CDS archive has aot-linked classes but the archived heap objects cannot be loaded)" + "|(Initial heap size set to a larger value than the maximum heap size)" + "|(java.lang.OutOfMemoryError)" + "|(Error: A JNI error has occurred, please check your installation and try again))"; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVMOptions.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVMOptions.java new file mode 100644 index 00000000000..05fdf7c06d6 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVMOptions.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2024, 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 + * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking + * @requires vm.flagless + * @summary Disable CDS when incompatible options related to AOTClassLinking are used + * @library /test/jdk/lib/testlibrary + * /test/lib + * /test/hotspot/jtreg/runtime/cds/appcds + * /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build Hello + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar Hello + * @run driver AOTClassLinkingVMOptions + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +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 AOTClassLinkingVMOptions { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + + static int testCaseNum = 0; + static void testCase(String s) { + testCaseNum++; + System.out.println("Test case " + testCaseNum + ": " + s); + } + + public static void main(String[] args) throws Exception { + TestCommon.testDump(appJar, TestCommon.list("Hello"), + "-XX:+AOTClassLinking"); + + testCase("Archived full module graph must be enabled at runtime"); + TestCommon.run("-cp", appJar, "-Djdk.module.validation=1", "Hello") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used when archived full module graph is not used"); + + testCase("Cannot use -Djava.system.class.loader"); + TestCommon.run("-cp", appJar, "-Djava.system.class.loader=dummy", "Hello") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used when the java.system.class.loader property is specified."); + + testCase("Cannot use a different main module"); + TestCommon.run("-cp", appJar, "-Xlog:cds", "-m", "jdk.compiler/com.sun.tools.javac.Main") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used when archived full module graph is not used."); + testCase("Cannot use security manager"); + TestCommon.run("-cp", appJar, "-Xlog:cds", "-Djava.security.manager=allow") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used with -Djava.security.manager=allow."); + TestCommon.run("-cp", appJar, "-Xlog:cds", "-Djava.security.manager=default") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used with -Djava.security.manager=default."); + + // NOTE: tests for ClassFileLoadHook + AOTClassLinking is in + // ../jvmti/ClassFileLoadHookTest.java + + boolean dynamicMode = Boolean.getBoolean("test.dynamic.cds.archive"); + if (!dynamicMode) { + // These tests need to dump the full module graph, which is not possible with + // dynamic dump. + modulePathTests(); + } + } + + static void modulePathTests() throws Exception { + CDSModulePathUtils.init(); + + testCase("Cannot use mis-matched module path"); + String goodModulePath = CDSModulePathUtils.getModulesDir().toString(); + TestCommon.testDump(null, CDSModulePathUtils.getAppClasses(), + "--module-path", goodModulePath, + "-XX:+AOTClassLinking", + "-m", CDSModulePathUtils.MAIN_MODULE); + TestCommon.run("-Xlog:cds", + "--module-path", goodModulePath, + "-m", CDSModulePathUtils.MAIN_MODULE) + .assertNormalExit("Using AOT-linked classes: true"); + + TestCommon.run("-Xlog:cds", + "--module-path", goodModulePath + "/bad", + "-m", CDSModulePathUtils.MAIN_MODULE) + .assertAbnormalExit("CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used."); + + testCase("Cannot use mis-matched --add-modules"); + TestCommon.testDump(null, CDSModulePathUtils.getAppClasses(), + "--module-path", goodModulePath, + "-XX:+AOTClassLinking", + "--add-modules", CDSModulePathUtils.MAIN_MODULE); + TestCommon.run("-Xlog:cds", + "--module-path", goodModulePath, + "--add-modules", CDSModulePathUtils.MAIN_MODULE, + CDSModulePathUtils.MAIN_CLASS) + .assertNormalExit("Using AOT-linked classes: true"); + + TestCommon.run("-Xlog:cds", + "--module-path", goodModulePath + "/bad", + "--add-modules", CDSModulePathUtils.TEST_MODULE, + CDSModulePathUtils.MAIN_CLASS) + .assertAbnormalExit("Mismatched --add-modules module name(s)", + "CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used."); + } +} + +// TODO: enhance and move this class to jdk.test.lib.cds.CDSModulePathUtils + +class CDSModulePathUtils { + private static String TEST_SRC = System.getProperty("test.root"); + private static Path USER_DIR = Paths.get(CDSTestUtils.getOutputDir()); + private static Path SRC_DIR = Paths.get(TEST_SRC, "runtime/cds/appcds/jigsaw/modulepath/src"); + private static Path MODS_DIR = Paths.get("mods"); + + public static String MAIN_MODULE = "com.bars"; + public static String TEST_MODULE = "com.foos"; + + public static String MAIN_CLASS = "com.bars.Main"; + public static String TEST_CLASS = "com.foos.Test"; + private static String appClasses[] = {MAIN_CLASS, TEST_CLASS}; + + private static Path modulesDir; + + // This directory contains all the modular jar files + // $USER_DIR/modules/com.bars.jar + // $USER_DIR/modules/com.foos.jar + static Path getModulesDir() { + return modulesDir; + } + + static String[] getAppClasses() { + return appClasses; + } + + static void init() throws Exception { + JarBuilder.compileModule(SRC_DIR.resolve(TEST_MODULE), + MODS_DIR.resolve(TEST_MODULE), + null); + JarBuilder.compileModule(SRC_DIR.resolve(MAIN_MODULE), + MODS_DIR.resolve(MAIN_MODULE), + MODS_DIR.toString()); + + String PATH_LIBS = "modules"; + modulesDir = Files.createTempDirectory(USER_DIR, PATH_LIBS); + Path mainJar = modulesDir.resolve(MAIN_MODULE + ".jar"); + Path testJar = modulesDir.resolve(TEST_MODULE + ".jar"); + + // modylibs contains both modules com.foos.jar, com.bars.jar + // build com.foos.jar + String classes = MODS_DIR.resolve(TEST_MODULE).toString(); + JarBuilder.createModularJar(testJar.toString(), classes, TEST_CLASS); + + // build com.bars.jar + classes = MODS_DIR.resolve(MAIN_MODULE).toString(); + JarBuilder.createModularJar(mainJar.toString(), classes, MAIN_CLASS); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java new file mode 100644 index 00000000000..49544f50032 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +// AOT-linked classes are loaded during VM bootstrap by the C++ class AOTLinkedClassBulkLoader. +// Make sure that the Module, Package, CodeSource and ProtectionDomain of these classes are +// set up properly. + +/* + * @test id=static + * @requires vm.cds.supports.aot.class.linking + * @library /test/jdk/lib/testlibrary /test/lib + * @build InitiatingLoaderTester + * @build BulkLoaderTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester + * @run driver BulkLoaderTest STATIC + */ + +/* + * @test id=dynamic + * @requires vm.cds.supports.aot.class.linking + * @library /test/jdk/lib/testlibrary /test/lib + * @build InitiatingLoaderTester + * @build BulkLoaderTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester + * @run driver BulkLoaderTest DYNAMIC + */ + +import java.io.File; +import java.lang.StackWalker.StackFrame; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.Set; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class BulkLoaderTest { + static final String appJar = ClassFileInstaller.getJarPath("BulkLoaderTestApp.jar"); + static final String mainClass = "BulkLoaderTestApp"; + + public static void main(String[] args) throws Exception { + Tester t = new Tester(); + + // Run with archived FMG loaded + t.run(args); + + // Run with an extra classpath -- archived FMG can still load. + { + String extraVmArgs[] = { + "-cp", + appJar + File.pathSeparator + "foobar.jar" + }; + OutputAnalyzer out = t.productionRun(extraVmArgs); + out.shouldHaveExitValue(0); + } + + // Run without archived FMG -- fail to load + { + String extraVmArgs[] = { + "-Xshare:on", + "-Xlog:cds", + "-Djdk.module.showModuleResolution=true" + }; + t.setCheckExitValue(false); + OutputAnalyzer out = t.productionRun(extraVmArgs); + out.shouldHaveExitValue(1); + out.shouldContain("CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used."); + t.setCheckExitValue(true); + } + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:cds,cds+aot+load", + "-XX:+AOTClassLinking", + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + }; + } + } +} + +class BulkLoaderTestApp { + static String allPerms = "null.*.*java.security.Permissions.*,*java.security.AllPermission.*.*"; + + public static void main(String args[]) throws Exception { + checkClasses(); + checkInitiatingLoader(); + } + + // Check the ClassLoader/Module/Package/ProtectionDomain/CodeSource of classes that are aot-linked + static void checkClasses() throws Exception { + check(String.class, + "null", // loader + "module java.base", + "package java.lang", + "null", + allPerms); + + check(Class.forName("sun.util.logging.internal.LoggingProviderImpl"), + "null", + "module java.logging", + "package sun.util.logging.internal", + "null", + allPerms); + + + check(javax.tools.FileObject.class, + "^jdk.internal.loader.ClassLoaders[$]PlatformClassLoader@", + "module java.compiler", + "package javax.tools", + "jrt:/java.compiler ", + "jdk.internal.loader.ClassLoaders[$]PlatformClassLoader.*.*java.security.Permissions"); + + check(BulkLoaderTestApp.class, + "jdk.internal.loader.ClassLoaders[$]AppClassLoader@", + "^unnamed module @", + "package ", + "file:.*BulkLoaderTestApp.jar ", + "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*.*java.security.Permissions"); + + check(Class.forName("com.sun.tools.javac.Main"), + "jdk.internal.loader.ClassLoaders[$]AppClassLoader@", + "module jdk.compiler", + "package com.sun.tools.javac", + "jrt:/jdk.compiler ", + "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*.*java.security.Permissions"); + + doit(() -> { + Class lambdaClass = MyUtil.getCallerClass(1); + check(lambdaClass, + "jdk.internal.loader.ClassLoaders[$]AppClassLoader@", + "unnamed module", + "package ", + "file:.*BulkLoaderTestApp.jar ", + "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*.*java.security.Permissions"); + + }); + } + + static void check(Class c, String loader, String module, String pkg, String codeSource, String protectionDomain) { + System.out.println("===================================================================="); + System.out.println(c.getName() + ", loader = " + c.getClassLoader()); + System.out.println(c.getName() + ", module = " + c.getModule()); + System.out.println(c.getName() + ", package = " + c.getPackage()); + System.out.println(c.getName() + ", CS = " + c.getProtectionDomain().getCodeSource()); + System.out.println(c.getName() + ", PD = " + c.getProtectionDomain()); + + expectMatch("" + c.getClassLoader(), loader); + expectMatch("" + c.getModule(), module); + expectSame("" + c.getPackage(), pkg); + expectMatch("" + c.getProtectionDomain().getCodeSource(), codeSource); + expectMatch("" + c.getProtectionDomain(), protectionDomain); + } + + static void expectSame(String a, String b) { + if (!a.equals(b)) { + throw new RuntimeException("Expected \"" + b + "\" but got \"" + a + "\""); + } + } + static void expectMatch(String string, String pattern) { + Matcher matcher = Pattern.compile(pattern, Pattern.DOTALL).matcher(string); + if (!matcher.find()) { + throw new RuntimeException("Expected pattern \"" + pattern + "\" but got \"" + string + "\""); + } + } + + static void doit(Runnable t) { + t.run(); + } + + static void checkInitiatingLoader() throws Exception { + try { + InitiatingLoaderTester.tryAccess(); + } catch (IllegalAccessError t) { + if (t.getMessage().contains("cannot access class jdk.internal.misc.Unsafe (in module java.base)")) { + System.out.println("Expected exception:"); + t.printStackTrace(System.out); + // Class.forName() should still work. We just can't resolve it in CP entries. + Class c = Class.forName("jdk.internal.misc.Unsafe"); + System.out.println("App loader can still resolve by name: " + c); + return; + } + throw new RuntimeException("Unexpected exception", t); + } + + throw new RuntimeException("Should not have succeeded"); + } +} + +class MyUtil { + // depth is 0-based -- i.e., depth==0 returns the class of the immediate caller of getCallerClass + static Class getCallerClass(int depth) { + // Need to add the frame of the getCallerClass -- so the immediate caller (depth==0) of this method + // is at stack.get(1) == stack.get(depth+1); + StackWalker walker = StackWalker.getInstance( + Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE, + StackWalker.Option.SHOW_HIDDEN_FRAMES)); + List stack = walker.walk(s -> s.limit(depth+2).collect(Collectors.toList())); + return stack.get(depth+1).getDeclaringClass(); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/InitiatingLoaderTester.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/InitiatingLoaderTester.jasm new file mode 100644 index 00000000000..ae1b38e20a9 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/InitiatingLoaderTester.jasm @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +/* + + +class InitiatingLoaderTester { + public static Object tryAccess() { + return jdk.internal.misc.Unsafe.getUnsafe(); + } +} + + + +*/ + + +super class InitiatingLoaderTester + version 66:0 +{ + Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + public static Method tryAccess:"()Ljava/lang/Object;" + stack 2 locals 0 + { + invokestatic Method jdk/internal/misc/Unsafe."getUnsafe":"()Ljdk/internal/misc/Unsafe;"; + areturn; + } + +} // end Class InitiatingLoaderTester diff --git a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java index f5975569447..1603d8430b2 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -27,7 +27,7 @@ * @bug 8214781 8293187 * @summary Test for the -XX:ArchiveHeapTestClass flag * @requires vm.debug == true & vm.cds.write.archived.java.heap - * @modules java.base/sun.invoke.util java.logging + * @modules java.logging * @library /test/jdk/lib/testlibrary /test/lib * /test/hotspot/jtreg/runtime/cds/appcds * /test/hotspot/jtreg/runtime/cds/appcds/test-classes @@ -35,12 +35,13 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar * CDSTestClassA CDSTestClassA$XX CDSTestClassA$YY * CDSTestClassB CDSTestClassC CDSTestClassD - * CDSTestClassE CDSTestClassF CDSTestClassG + * CDSTestClassE CDSTestClassF CDSTestClassG CDSTestClassG$MyEnum CDSTestClassG$Wrapper * pkg.ClassInPackage * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar Hello * @run driver ArchiveHeapTestClass */ +import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.Platform; import jdk.test.lib.helpers.ClassFileInstaller; import jdk.test.lib.process.OutputAnalyzer; @@ -151,19 +152,24 @@ public class ArchiveHeapTestClass { output = dumpBootAndHello(CDSTestClassD_name); mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassD::archivedObjects"); - testCase("Use a disallowed class: in unnamed module but not in unname package"); - output = dumpBootAndHello(CDSTestClassE_name); - mustFail(output, "Class pkg.ClassInPackage not allowed in archive heap"); + if (!CDSTestUtils.isAOTClassLinkingEnabled()) { + testCase("Use a disallowed class: in unnamed module but not in unname package"); + output = dumpBootAndHello(CDSTestClassE_name); + mustFail(output, "Class pkg.ClassInPackage not allowed in archive heap"); - testCase("Use a disallowed class: not in java.base module"); - output = dumpBootAndHello(CDSTestClassF_name); - mustFail(output, "Class java.util.logging.Level not allowed in archive heap"); - - if (false) { // JDK-8293187 - testCase("sun.invoke.util.Wrapper"); - output = dumpBootAndHello(CDSTestClassG_name); - mustSucceed(output); + testCase("Use a disallowed class: not in java.base module"); + 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:cds+heap,cds+init", + CDSTestClassG_name) + .assertNormalExit("init subgraph " + CDSTestClassG_name, + "Initialized from CDS"); } } @@ -171,12 +177,27 @@ class CDSTestClassA { static final String output = "CDSTestClassA. was executed"; static Object[] archivedObjects; static { - archivedObjects = new Object[5]; - archivedObjects[0] = output; - archivedObjects[1] = new CDSTestClassA[0]; - archivedObjects[2] = new YY(); - archivedObjects[3] = new int[0]; - archivedObjects[4] = new int[2][2]; + // The usual convention would be to call this here: + // CDS.initializeFromArchive(CDSTestClassA.class); + // However, the CDS class is not exported to the unnamed module by default, + // and we don't want to use "--add-exports java.base/jdk.internal.misc=ALL-UNNAMED", as + // that would disable the archived full module graph, which will disable + // CDSConfig::is_using_aot_linked_classes(). + // + // Instead, HeapShared::initialize_test_class_from_archive() will set up the + // "archivedObjects" field first, before calling CDSTestClassA.. So + // if we see that archivedObjects is magically non-null here, that means + // it has been restored from the CDS archive. + if (archivedObjects == null) { + archivedObjects = new Object[5]; + archivedObjects[0] = output; + archivedObjects[1] = new CDSTestClassA[0]; + archivedObjects[2] = new YY(); + archivedObjects[3] = new int[0]; + archivedObjects[4] = new int[2][2]; + } else { + System.out.println("Initialized from CDS"); + } System.out.println(output); System.out.println("CDSTestClassA module = " + CDSTestClassA.class.getModule()); System.out.println("CDSTestClassA package = " + CDSTestClassA.class.getPackage()); @@ -269,8 +290,143 @@ class CDSTestClassF { class CDSTestClassG { static Object[] archivedObjects; static { - // Not in java.base - archivedObjects = new Object[1]; - archivedObjects[0] = sun.invoke.util.Wrapper.BOOLEAN; + 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; } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/CustomClassListDump.java b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/CustomClassListDump.java index 625ef1fbc92..27072bea35e 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/CustomClassListDump.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/CustomClassListDump.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -94,7 +94,7 @@ public class CustomClassListDump { .shouldContain("unreg CustomLoadee") .shouldContain("unreg CustomLoadee2") .shouldContain("unreg CustomLoadee3Child") - .shouldContain("unreg OldClass ** unlinked"); + .shouldContain("unreg OldClass old unlinked"); // Use the dumped static archive opts = (new CDSOptions()) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java index 5751f62e148..7ccd4e5d312 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, 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 @@ -96,5 +96,23 @@ public class ClassFileLoadHookTest { "ClassFileLoadHook", "" + ClassFileLoadHook.TestCaseId.SHARING_ON_CFLH_ON); TestCommon.checkExec(out); + + // JEP 483: if dumped with -XX:+AOTClassLinking, cannot use archive when CFLH is enabled + TestCommon.testDump(appJar, sharedClasses, useWb, "-XX:+AOTClassLinking"); + out = TestCommon.exec(appJar, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", useWb, + "-agentlib:SimpleClassFileLoadHook=LoadMe,beforeHook,after_Hook", + "-Xlog:cds", + "ClassFileLoadHook", + "" + ClassFileLoadHook.TestCaseId.SHARING_ON_CFLH_ON); + if (out.contains("Using AOT-linked classes: false (static archive: no aot-linked classes")) { + // JTREG is executed with VM options that do not support -XX:+AOTClassLinking, so + // the static archive was not created with aot-linked classes. + out.shouldHaveExitValue(0); + } else { + out.shouldContain("CDS archive has aot-linked classes. It cannot be used when JVMTI ClassFileLoadHook is in use."); + out.shouldNotHaveExitValue(0); + } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java index 8ca8b7d952e..7581b2367aa 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java @@ -61,8 +61,8 @@ public class OldClassAndRedefineClass { String agentCmdArg = "-javaagent:redefineagent.jar"; OutputAnalyzer out = TestCommon.testDump(appJar, sharedClasses, "-Xlog:cds,cds+class=debug"); - out.shouldMatch("klasses.*OldSuper.[*][*].unlinked") - .shouldMatch("klasses.*ChildOldSuper.[*][*].unlinked"); + out.shouldMatch("klasses.*OldSuper.* unlinked") + .shouldMatch("klasses.*ChildOldSuper.* unlinked"); out = TestCommon.exec( appJar, diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java new file mode 100644 index 00000000000..5fb0a30cd61 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2024, 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 AOT resolution of lambda expressions + * @bug 8340836 + * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/ + * @build AOTLinkedLambdas + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * AOTLinkedLambdasApp InitTracker + * IntfWithNoClinit IntfWithNoClinit2 + * IA IB IC ID1 ID2 IE1 IE2 IF1 IF2 IG1 IG2 IH1 IH2 IH3 + * FooA FooB + * BarA BarB BarC + * @run driver AOTLinkedLambdas + */ + +import java.util.function.Supplier; +import static java.util.stream.Collectors.*; +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class AOTLinkedLambdas { + static final String classList = "AOTLinkedLambdas.classlist"; + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = AOTLinkedLambdasApp.class.getName(); + + public static void main(String[] args) throws Exception { + CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Hello AOTLinkedLambdasApp"); + }); + + CDSOptions opts = (new CDSOptions()) + .addPrefix("-XX:ExtraSharedClassListFile=" + classList, + "-XX:+AOTClassLinking", + "-Xlog:cds+resolve=trace", + "-Xlog:cds+class=debug", + "-cp", appJar); + + OutputAnalyzer dumpOut = CDSTestUtils.createArchiveAndCheck(opts); + dumpOut.shouldContain("Can aot-resolve Lambda proxy of interface type IA"); + dumpOut.shouldContain("Can aot-resolve Lambda proxy of interface type IB"); + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IC"); + dumpOut.shouldContain("Can aot-resolve Lambda proxy of interface type ID2"); + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IE2"); // unsupported = IE1 + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IF2"); + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IG2"); + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IH3"); // unsupported = IH1 + + CDSOptions runOpts = (new CDSOptions()) + .setUseVersion(false) + .addPrefix("-Xlog:cds", + "-esa", // see JDK-8340836 + "-cp", appJar) + .addSuffix(mainClass); + + CDSTestUtils.run(runOpts) + .assertNormalExit("Hello AOTLinkedLambdasApp", + "hello, world"); + } +} + +class AOTLinkedLambdasApp { + static { + System.out.println("AOTLinkedLambdasApp."); + } + public static void main(String args[]) { + System.out.println("Hello AOTLinkedLambdasApp"); + + // (1) Simple tests + var words = java.util.List.of("hello", "fuzzy", "world"); + System.out.println(words.stream().filter(w->!w.contains("u")).collect(joining(", "))); + // => hello, world + + // (2) Test for order. + testClinitOrder(); + } + + + // Check that aot-linking of lambdas does not cause to be skipped or + // otherwise executed in the wrong order. + // + // A lambda is declared to implement an interface X, but it also implicitly + // implements all super interfaces of X. + // + // For any interface IN that's implemented by a lambda, if IN has declared + // a non-abstract, non-static method (JVMS 5.5. Initialization), IN must be + // initialized before the lambda can be linked. If IN:: exists, the + // initialization of IN can have side effects. + // + // AOTConstantPoolResolver::is_indy_resolution_deterministic() excludes + // any lambda if initializing its interfaces can cause side effects. This test + // checks that such exclusions are working as expected. + // + // This test also proves that is_indy_resolution_deterministic() doen't need to check + // for all other types that are mentioned by the lambda call site, as those classes + // will not be initialized as part of linking the lambda. + static void testClinitOrder() { + /* + * An indy callsite is associated with the following MethodType and MethodHandles: + * + * https://github.com/openjdk/jdk/blob/580eb62dc097efeb51c76b095c1404106859b673/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java#L293-L309 + * + * MethodType factoryType The expected signature of the {@code CallSite}. The + * parameter types represent the types of capture variables; + * the return type is the interface to implement. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * + * MethodType interfaceMethodType Signature and return type of method to be + * implemented by the function object. + * + * MethodHandle implementation A direct method handle describing the implementation + * method which should be called (with suitable adaptation + * of argument types and return types, and with captured + * arguments prepended to the invocation arguments) at + * invocation time. + * + * MethodType dynamicMethodType The signature and return type that should + * be enforced dynamically at invocation time. + * In simple use cases this is the same as + * {@code interfaceMethodType}. + */ + + // Initial condition: no used by our Foo* and Bar* types have been called. + InitTracker.assertOrder("InitTracker"); + + //============================== + // Case (i) -- Check for types used by factoryType, interfaceMethodType and dynamicMethodType + // (Note: no tests for captured variables in factoryType yet; will be tested in case (ii)) + // factoryType = "()LIntfWithNoClinit; + // interfaceMethodType = "(LFooB;)LFooA;" + // implementation = "REF_invokeStatic AOTLinkedLambdasApp.implAB:(LBarB;)LBarA;" + // dynamicMethodType = "(LBarB;)LBarA;" + IntfWithNoClinit noclinit = AOTLinkedLambdasApp::implAB; + + // None of the Foo? and Bar? types used by the lambda should have been initialized yet, even though + // the indy callsite has been resolved now. + InitTracker.assertOrder("InitTracker"); + + BarB barB = new BarB(); + InitTracker.assertOrder("InitTracker, FooB, BarB"); + BarA barA = noclinit.doit(barB); + System.out.println(barB); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA"); + + //============================== + // Case (ii) -- Check for types used by captured variables in factoryType + BarC barC = null; + IntfWithNoClinit2 noclinit2 = () -> { return barC.hashCode(); }; + try { + noclinit2.doit(); + throw new RuntimeException("NullPointerException should have been thrown"); + } catch (NullPointerException npe) { + // expected + } + // BarC shouldn't be initialized as no instances of it has been created. + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA"); + + + //============================== + // (IA) No default methods -- is not initialized during lambda linking. Lambda can be archived. + IA ia = () -> {}; + ia.doit(); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA"); + System.out.println(IA._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA"); + + //============================== + // (IB) Has default method but has not -- OK to initialize IB during lambda linking. Lambda can be archived. + IB ib = () -> {}; + ib.doit(); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA"); + + //============================== + // (IC) Has both default method and -- cannot AOT link the lambda + IC ic = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC"); + ic.doit(); + + //============================== + // ID1 - has default method, but no + // ID2 - has , but no default method + ID2 id2 = () -> {}; + id2.doit(); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC"); + System.out.println(ID2._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2"); + + //============================== + // IE1 - has both default method and + // IE2 - has , but no default method + IE2 ie2 = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1"); + System.out.println(IE2._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2"); + + //============================== + // IF1 - has , but no default method + // IF2 - has both default method and + IF2 if2 = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2"); + System.out.println(IF1._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1"); + + //============================== + // IG1 - has both default method and + // IG2 - has both default method and + IG2 ig2 = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1, IG1, IG2"); + + //============================== + // Similar to IE1/IE2, but IH3 is one more level away from IH1 + // IH1 - has both default method and + // IH2 - has , but no default method + // IH3 - has , but no default method + IH3 ih3 = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1, IG1, IG2, IH1"); + System.out.println(IH3._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1, IG1, IG2, IH1, IH3"); + System.out.println(IH2._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1, IG1, IG2, IH1, IH3, IH2"); + } + + static BarA implAB(BarB param) { + return new BarA(param); + } +} + + +// An interface with no method. A lambda implementing this +// interface can be AOT-linked. +@FunctionalInterface +interface IntfWithNoClinit { + X doit(Y param); +} + +// Another interface with no method. A lambda implementing this +// interface can be AOT-linked. +@FunctionalInterface +interface IntfWithNoClinit2 { + int doit(); +} + + +// (IA) No default methods -- is not initialized during lambda linking. Lambda can be archived. +@FunctionalInterface interface IA { + static int _dummy = InitTracker.trackEvent("IA"); + void doit(); +} + +// (IB) Has default method but has not -- OK to initialize IB during lambda linking. Lambda can be archived. +@FunctionalInterface interface IB { + default int dummy() { return 0; } + void doit(); +} + +// (IC) Has both default method and -- cannot AOT link the lambda +@FunctionalInterface interface IC { + static int _dummy = InitTracker.trackEvent("IC"); + default int dummy() { return _dummy; } + void doit(); +} + +// (ID1/ID2) +@FunctionalInterface interface ID1 { // has default method, but no + default int dummy() { return 0; } + void doit(); +} + +@FunctionalInterface interface ID2 extends ID1 { // has , but no default method + static int _dummy = InitTracker.trackEvent("ID2"); +} + +// (IE1/IE2) +@FunctionalInterface interface IE1 { // has default method and + static int _dummy = InitTracker.trackEvent("IE1"); + default int dummy() { return _dummy; } + void doit(); +} + +@FunctionalInterface interface IE2 extends IE1 { // has , but no default method + static int _dummy = InitTracker.trackEvent("IE2"); +} + +// (IF1/IF2) +@FunctionalInterface interface IF1 { // has , but no default method + static int _dummy = InitTracker.trackEvent("IF1"); + void doit(); +} + +@FunctionalInterface interface IF2 extends IF1 { // has default method and + static int _dummy = InitTracker.trackEvent("IF2"); + default int dummy() { return 0; } +} + +// (IG1/IG2) +@FunctionalInterface interface IG1 { // has default method and + static int _dummy = InitTracker.trackEvent("IG1"); + default int dummy() { return _dummy; } + void doit(); +} + +@FunctionalInterface interface IG2 extends IG1 { // has default method and + static int _dummy = InitTracker.trackEvent("IG2"); + default int dummy() { return _dummy; } +} + +// (IH1/IH2/IH3) +@FunctionalInterface interface IH1 { // has default method and + static int _dummy = InitTracker.trackEvent("IH1"); + default int dummy() { return _dummy; } + void doit(); +} + +@FunctionalInterface interface IH2 extends IH1 { // has but no default method + static int _dummy = InitTracker.trackEvent("IH2"); +} + +@FunctionalInterface interface IH3 extends IH2 { // has but no default method + static int _dummy = InitTracker.trackEvent("IH3"); +} + + +class InitTracker { + static String actualOrder = "InitTracker"; + static int trackEvent(String event) { + actualOrder += ", " + event; + return actualOrder.lastIndexOf(','); + } + static void assertOrder(String wantOrder) { + System.out.println("wantOrder = " + wantOrder); + System.out.println("actualOrder = " + actualOrder); + if (!actualOrder.equals(wantOrder)) { + throw new RuntimeException("Want order: {" + wantOrder + "}, but got {" + actualOrder + "}"); + } + } +} + +interface FooA { + static final int _dummy = InitTracker.trackEvent("FooA"); + default int dummy() { return _dummy; } +} + +interface FooB { + static final int _dummy = InitTracker.trackEvent("FooB"); + default int dummy() { return _dummy; } +} + +class BarA implements FooA { + static {InitTracker.trackEvent("BarA");} + BarA(BarB dummy) {} +} + +class BarB implements FooB { + static {InitTracker.trackEvent("BarB");} +} + +class BarC { + static {InitTracker.trackEvent("BarC");} +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java new file mode 100644 index 00000000000..2098ebc2c71 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, 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 AOT resolution of VarHandle invocation + * @bug 8343245 + * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib + * @build AOTLinkedVarHandles + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * AOTLinkedVarHandlesApp AOTLinkedVarHandlesApp$Data + * @run driver AOTLinkedVarHandles + */ + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class AOTLinkedVarHandles { + static final String classList = "AOTLinkedVarHandles.classlist"; + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = AOTLinkedVarHandlesApp.class.getName(); + + public static void main(String[] args) throws Exception { + CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Hello AOTLinkedVarHandlesApp"); + }); + + CDSOptions opts = (new CDSOptions()) + .addPrefix("-XX:ExtraSharedClassListFile=" + classList, + "-XX:+AOTClassLinking", + "-Xlog:cds+resolve=trace", + "-Xlog:cds+class=debug", + "-cp", appJar); + + String s = "archived method CP entry.* AOTLinkedVarHandlesApp "; + OutputAnalyzer dumpOut = CDSTestUtils.createArchiveAndCheck(opts); + dumpOut.shouldMatch(s + "java/lang/invoke/VarHandle.compareAndExchangeAcquire:\\(\\[DIDI\\)D =>"); + dumpOut.shouldMatch(s + "java/lang/invoke/VarHandle.get:\\(\\[DI\\)D => "); + + CDSOptions runOpts = (new CDSOptions()) + .setUseVersion(false) + .addPrefix("-Xlog:cds", + "-esa", + "-cp", appJar) + .addSuffix(mainClass); + + CDSTestUtils.run(runOpts) + .assertNormalExit("Hello AOTLinkedVarHandlesApp"); + } +} + +class AOTLinkedVarHandlesApp { + static final VarHandle initialized; + static final VarHandle lazy; + static long longField = 5678; + static long seed; + + static { + try { + lazy = MethodHandles.lookup().findStaticVarHandle(Data.class, "longField", long.class); + initialized = MethodHandles.lookup().findStaticVarHandle(AOTLinkedVarHandlesApp.class, "longField", long.class); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + static class Data { + static long longField = seed; + } + + public static void main(String args[]) { + seed = 1234; + System.out.println("Hello AOTLinkedVarHandlesApp"); + long a = (long) lazy.get(); + long b = (long) initialized.get(); + System.out.println(a); + System.out.println(b); + if (a != 1234) { + throw new RuntimeException("Data class should not be initialized: " + a); + } + if (b != 5678) { + throw new RuntimeException("VarHandle.get() failed: " + b); + } + + VarHandle vh = MethodHandles.arrayElementVarHandle(double[].class); + double[] array = new double[] {1.0}; + int index = 0; + int v = 4; + + // JDK-8343245 -- this generates "java.lang.invoke.LambdaForm$VH/0x80????" hidden class + double r = (double) vh.compareAndExchangeAcquire(array, index, 1.0, v); + if (r != 1.0) { + throw new RuntimeException("Unexpected result: " + r); + } + r = (double) vh.get(array, index); + if (r != 4.0) { + throw new RuntimeException("Unexpected result: " + r); + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java index 474fa65d6ea..7d3335b9db6 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java @@ -24,26 +24,40 @@ /* * @test - * @summary Dump time resolutiom of constant pool entries. + * @summary Dump time resolution of constant pool entries. * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking * @requires vm.compMode != "Xcomp" - * @library /test/lib + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/ + * @build OldProvider OldClass OldConsumer StringConcatTestOld * @build ResolvedConstants - * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar ResolvedConstantsApp ResolvedConstantsFoo ResolvedConstantsBar + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * ResolvedConstantsApp ResolvedConstantsFoo ResolvedConstantsBar + * MyInterface InterfaceWithClinit NormalClass + * OldProvider OldClass OldConsumer SubOfOldClass + * StringConcatTest StringConcatTestOld * @run driver ResolvedConstants */ +import java.util.function.Consumer; import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; public class ResolvedConstants { static final String classList = "ResolvedConstants.classlist"; static final String appJar = ClassFileInstaller.getJarPath("app.jar"); static final String mainClass = ResolvedConstantsApp.class.getName(); + static boolean aotClassLinking; public static void main(String[] args) throws Exception { - // dump class list + test(false); + test(true); + } + + static void test(boolean testMode) throws Exception { + aotClassLinking = testMode; CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass) .assertNormalExit(output -> { output.shouldContain("Hello ResolvedConstantsApp"); @@ -52,78 +66,109 @@ public class ResolvedConstants { CDSOptions opts = (new CDSOptions()) .addPrefix("-XX:ExtraSharedClassListFile=" + classList, "-cp", appJar, - "-Xlog:cds+resolve=trace"); - CDSTestUtils.createArchiveAndCheck(opts) + "-Xlog:cds+resolve=trace", + "-Xlog:cds+class=debug"); + if (aotClassLinking) { + opts.addPrefix("-XX:+AOTClassLinking"); + } else { + opts.addPrefix("-XX:-AOTClassLinking"); + } + + OutputAnalyzer out = CDSTestUtils.createArchiveAndCheck(opts); // Class References --- // Always resolve reference when a class references itself - .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => ResolvedConstantsApp app") + out.shouldMatch(ALWAYS("klass.* ResolvedConstantsApp app => ResolvedConstantsApp app")) // Always resolve reference when a class references a super class - .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => java/lang/Object boot") - .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsBar app => ResolvedConstantsFoo app") + .shouldMatch(ALWAYS("klass.* ResolvedConstantsApp app => java/lang/Object boot")) + .shouldMatch(ALWAYS("klass.* ResolvedConstantsBar app => ResolvedConstantsFoo app")) // Always resolve reference when a class references a super interface - .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => java/lang/Runnable boot") + .shouldMatch(ALWAYS("klass.* ResolvedConstantsApp app => java/lang/Runnable boot")) - // java/lang/System is in the root loader but ResolvedConstantsApp is loaded by the app loader. - // Even though System is in the vmClasses list, when ResolvedConstantsApp looks up - // "java/lang/System" in its ConstantPool, the app loader may not have resolved the System - // class yet (i.e., there's no initiaited class entry for System in the app loader's dictionary) - .shouldMatch("cds,resolve.*reverted klass.* ResolvedConstantsApp .*java/lang/System") + // Without -XX:+AOTClassLinking: + // java/lang/System is in the boot loader but ResolvedConstantsApp is loaded by the app loader. + // Even though System is in the vmClasses list, when ResolvedConstantsApp looks up + // "java/lang/System" in its ConstantPool, the app loader may not have resolved the System + // class yet (i.e., there's no initiaited class entry for System in the app loader's dictionary) + .shouldMatch(AOTLINK_ONLY("klass.* ResolvedConstantsApp .*java/lang/System")) // Field References --- // Always resolve references to fields in the current class or super class(es) - .shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsBar.b:I") - .shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsBar.a:I") - .shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsFoo.a:I") + .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsBar.b:I")) + .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsBar.a:I")) + .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsFoo.a:I")) + .shouldMatch(ALWAYS("field.* ResolvedConstantsFoo => ResolvedConstantsFoo.a:I")) - // Do not resolve field references to child classes - .shouldMatch("cds,resolve.*archived field.* ResolvedConstantsFoo => ResolvedConstantsFoo.a:I") - .shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsFoo ResolvedConstantsBar.a:I") - .shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsFoo ResolvedConstantsBar.b:I") + // Resolve field references to child classes ONLY when using -XX:+AOTClassLinking + .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsFoo => ResolvedConstantsBar.a:I")) + .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsFoo => ResolvedConstantsBar.b:I")) - // Do not resolve field references to unrelated classes - .shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsApp ResolvedConstantsBar.a:I") - .shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsApp ResolvedConstantsBar.b:I") + // Resolve field references to unrelated classes ONLY when using -XX:+AOTClassLinking + .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsApp => ResolvedConstantsBar.a:I")) + .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsApp => ResolvedConstantsBar.b:I")) // Method References --- // Should resolve references to own constructor - .shouldMatch("cds,resolve.*archived method .* ResolvedConstantsApp ResolvedConstantsApp.:") + .shouldMatch(ALWAYS("method.* ResolvedConstantsApp ResolvedConstantsApp.:")) // Should resolve references to super constructor - .shouldMatch("cds,resolve.*archived method .* ResolvedConstantsApp java/lang/Object.:") + .shouldMatch(ALWAYS("method.* ResolvedConstantsApp java/lang/Object.:")) // Should resolve interface methods in VM classes - .shouldMatch("cds,resolve.*archived interface method .* ResolvedConstantsApp java/lang/Runnable.run:") + .shouldMatch(ALWAYS("interface method .* ResolvedConstantsApp java/lang/Runnable.run:")) // Should resolve references to own non-static method (private or public) - .shouldMatch("archived method.*: ResolvedConstantsBar ResolvedConstantsBar.doBar:") - .shouldMatch("archived method.*: ResolvedConstantsApp ResolvedConstantsApp.privateInstanceCall:") - .shouldMatch("archived method.*: ResolvedConstantsApp ResolvedConstantsApp.publicInstanceCall:") + .shouldMatch(ALWAYS("method.*: ResolvedConstantsBar ResolvedConstantsBar.doBar:")) + .shouldMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.privateInstanceCall:")) + .shouldMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.publicInstanceCall:")) // Should not resolve references to static method - .shouldNotMatch(" archived method CP entry.*: ResolvedConstantsApp ResolvedConstantsApp.staticCall:") + .shouldNotMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.staticCall:")) // Should resolve references to method in super type - .shouldMatch(" archived method CP entry.*: ResolvedConstantsBar ResolvedConstantsFoo.doBar:") + .shouldMatch(ALWAYS("method.*: ResolvedConstantsBar ResolvedConstantsFoo.doBar:")) - // App class cannot resolve references to methods in boot classes: + // Without -XX:+AOTClassLinking App class cannot resolve references to methods in boot classes: // When the app class loader tries to resolve a class X that's normally loaded by // the boot loader, it's possible for the app class loader to get a different copy of // X (by using MethodHandles.Lookup.defineClass(), etc). Therefore, let's be on // the side of safety and revert all such references. - // - // This will be addressed in JDK-8315737. - .shouldMatch("reverted method.*: ResolvedConstantsApp java/io/PrintStream.println:") - .shouldMatch("reverted method.*: ResolvedConstantsBar java/lang/Class.getName:") + .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsApp java/io/PrintStream.println:")) + .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsBar java/lang/Class.getName:")) - // Should not resolve methods in unrelated classes. - .shouldMatch("reverted method.*: ResolvedConstantsApp ResolvedConstantsBar.doit:") + // Resole resolve methods in unrelated classes ONLY when using -XX:+AOTClassLinking + .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsApp ResolvedConstantsBar.doit:")) // End --- ; + + + // Indy References --- + if (aotClassLinking) { + out.shouldContain("Cannot aot-resolve Lambda proxy because OldConsumer is excluded") + .shouldContain("Cannot aot-resolve Lambda proxy because OldProvider is excluded") + .shouldContain("Cannot aot-resolve Lambda proxy because OldClass is excluded") + .shouldContain("Cannot aot-resolve Lambda proxy of interface type InterfaceWithClinit") + .shouldMatch("klasses.* app *NormalClass[$][$]Lambda/.* hidden aot-linked inited") + .shouldNotMatch("klasses.* app *SubOfOldClass[$][$]Lambda/") + .shouldMatch("archived indy *CP entry.*StringConcatTest .* => java/lang/invoke/StringConcatFactory.makeConcatWithConstants") + .shouldNotMatch("archived indy *CP entry.*StringConcatTestOld .* => java/lang/invoke/StringConcatFactory.makeConcatWithConstants"); + } + } + + static String ALWAYS(String s) { + return "cds,resolve.*archived " + s; + } + + static String AOTLINK_ONLY(String s) { + if (aotClassLinking) { + return ALWAYS(s); + } else { + return "cds,resolve.*reverted " + s; + } } } @@ -142,12 +187,98 @@ class ResolvedConstantsApp implements Runnable { bar.a ++; bar.b ++; bar.doit(); + + testLambda(); + StringConcatTest.test(); + StringConcatTestOld.main(null); } private static void staticCall() {} private void privateInstanceCall() {} public void publicInstanceCall() {} public void run() {} + + static void testLambda() { + // The functional type used in the Lambda is an excluded class + OldProvider op = () -> { + return null; + }; + + // A captured value is an instance of an excluded Class + OldClass c = new OldClass(); + Runnable r = () -> { + System.out.println("Test 1 " + c); + }; + r.run(); + + // The functional interface accepts an argument that's an excluded class + MyInterface i = (o) -> { + System.out.println("Test 2 " + o); + }; + i.dispatch(c); + + // Method reference to old class + OldConsumer oldConsumer = new OldConsumer(); + Consumer wrapper = oldConsumer::consumeString; + wrapper.accept("Hello"); + + // Lambda of interfaces that have are not archived. + InterfaceWithClinit i2 = () -> { + System.out.println("Test 3"); + }; + i2.dispatch(); + + // These two classes have almost identical source code, but + // only NormalClass should have its lambdas pre-resolved. + // SubOfOldClass is "old" -- it should be excluded from the AOT cache, + // so none of its lambda proxies should be cached + NormalClass.testLambda(); // Lambda proxy should be cached + SubOfOldClass.testLambda(); // Lambda proxy shouldn't be cached + } +} + +class StringConcatTest { + static void test() { + System.out.println("StringConcatTest " + new StringConcatTest()); // concat should be aot-resolved + } +} + +/* see StringConcatTestOld.jasm + +class StringConcatTestOld { + public static void main(String args[]) { + // concat should be aot-resolved => the MethodType refers to an old class + System.out.println("StringConcatTestOld " + new OldConsumer()); + } +} +*/ + +class NormalClass { + static void testLambda() { + Runnable r = () -> { + System.out.println("NormalClass testLambda"); + }; + r.run(); + } +} + +class SubOfOldClass extends OldClass { + static void testLambda() { + Runnable r = () -> { + System.out.println("SubOfOldClass testLambda"); + }; + r.run(); + } +} + +interface MyInterface { + void dispatch(OldClass c); +} + +interface InterfaceWithClinit { + static final long X = System.currentTimeMillis(); + void dispatch(); + default long dummy() { return X; } } class ResolvedConstantsFoo { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/StringConcatTestOld.jasm b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/StringConcatTestOld.jasm new file mode 100644 index 00000000000..76460cad0a3 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/StringConcatTestOld.jasm @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +/* + +decompiled from + +class OldConsumer {} + +class StringConcatTestOld { + public static void main(String args[]) { + System.out.println("StringConcatTestOld " + new OldConsumer()); + } +} + + +(1) Comment out this line + + invokestatic Method java/lang/String.valueOf:"(Ljava/lang/Object;)Ljava/lang/String;"; + +(2) Change the MethodType parameter of makeConcatWithConstants from + + "(Ljava/lang/String;)Ljava/lang/String;" + -> + + "(LOldConsumer;)Ljava/lang/String;" + +*/ + +super class StringConcatTestOld + version 67:0 +{ + Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + public static Method main:"([Ljava/lang/String;)V" + stack 3 locals 1 + { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + new class OldConsumer; + dup; + invokespecial Method OldConsumer."":"()V"; + //invokestatic Method java/lang/String.valueOf:"(Ljava/lang/Object;)Ljava/lang/String;"; + invokedynamic InvokeDynamic REF_invokeStatic:Method java/lang/invoke/StringConcatFactory.makeConcatWithConstants:"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;":makeConcatWithConstants:"(LOldConsumer;)Ljava/lang/String;" { + String "StringConcatTestOld " + }; + invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; + return; + } + + public static final InnerClass Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles; + +} // end Class StringConcatTestOld diff --git a/test/hotspot/jtreg/runtime/cds/appcds/test-classes/OldConsumer.jasm b/test/hotspot/jtreg/runtime/cds/appcds/test-classes/OldConsumer.jasm new file mode 100644 index 00000000000..bffb5a27381 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/test-classes/OldConsumer.jasm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, 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. + * + */ +super class OldConsumer + version 49:0 +{ + + +Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + +public Method consumeString:"(Ljava/lang/String;)V" + stack 3 locals 2 +{ + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + new class java/lang/StringBuilder; + dup; + invokespecial Method java/lang/StringBuilder."":"()V"; + ldc String "Hello: "; + invokevirtual Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;"; + aload_1; + invokevirtual Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;"; + invokevirtual Method java/lang/StringBuilder.toString:"()Ljava/lang/String;"; + invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; + return; +} + +} // end Class OldConsumer diff --git a/test/jdk/ProblemList-AotJdk.txt b/test/jdk/ProblemList-AotJdk.txt new file mode 100644 index 00000000000..9030ba60c78 --- /dev/null +++ b/test/jdk/ProblemList-AotJdk.txt @@ -0,0 +1,11 @@ +java/math/BigInteger/largeMemory/DivisionOverflow.java 0000000 generic-all +java/math/BigInteger/largeMemory/StringConstructorOverflow.java 0000000 generic-all + +jdk/internal/misc/CDS/ArchivedEnumTest.java 0000000 generic-all + +java/lang/module/ModuleDescriptorHashCodeTest.java 0000000 generic-all + + +# The test case is incorrect. There's no guarantee that running a JVM with the following +# parameters will always cause the class java/lang/invoke/MethodHandleStatics to be initialized +java/lang/invoke/DumpMethodHandleInternals.java 0000000 generic-all diff --git a/test/jdk/jdk/internal/misc/CDS/ArchivedEnumTest.java b/test/jdk/jdk/internal/misc/CDS/ArchivedEnumTest.java index ec1bd213834..3a2d0f9539f 100644 --- a/test/jdk/jdk/internal/misc/CDS/ArchivedEnumTest.java +++ b/test/jdk/jdk/internal/misc/CDS/ArchivedEnumTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -35,6 +35,7 @@ * @run driver ArchivedEnumTest */ +import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.helpers.ClassFileInstaller; import jdk.test.lib.process.OutputAnalyzer; @@ -46,7 +47,7 @@ public class ArchivedEnumTest { TestCommon.list("ArchivedEnumApp")); // Note: You can get the following line to fail by commenting out // the ADD_EXCL(...) lines in cdsHeapVerifier.cpp - out.shouldNotContain("object points to a static field that may be reinitialized at runtime"); + out.shouldNotContain(CDSTestUtils.MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); TestCommon.run("-cp", appJar, "-Xlog:cds=debug", diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 775b95959c6..5544ad1bebd 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -122,6 +122,7 @@ public class VMProps implements Callable> { // vm.cds is true if the VM is compiled with cds support. map.put("vm.cds", this::vmCDS); map.put("vm.cds.custom.loaders", this::vmCDSForCustomLoaders); + map.put("vm.cds.supports.aot.class.linking", this::vmCDSSupportsAOTClassLinking); map.put("vm.cds.write.archived.java.heap", this::vmCDSCanWriteArchivedJavaHeap); map.put("vm.continuations", this::vmContinuations); // vm.graal.enabled is true if Graal is used as JIT @@ -457,6 +458,14 @@ public class VMProps implements Callable> { && isCDSRuntimeOptionsCompatible()); } + /** + * @return true if this VM can support the -XX:AOTClassLinking option + */ + protected String vmCDSSupportsAOTClassLinking() { + // Currently, the VM supports AOTClassLinking as long as it's able to write archived java heap. + return vmCDSCanWriteArchivedJavaHeap(); + } + /** * @return true if the VM options specified via the "test.cds.runtime.options" * property is compatible with writing Java heap objects into the CDS archive diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java index c39e6bb8e94..d83c3c36e41 100644 --- a/test/lib/jdk/test/lib/cds/CDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -28,6 +28,7 @@ import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.StringArrayUtils; +import jtreg.SkippedException; /* * This is a base class used for testing CDS functionalities with complex applications. @@ -42,9 +43,13 @@ abstract public class CDSAppTester { private final String staticArchiveFileLog; private final String dynamicArchiveFile; private final String dynamicArchiveFileLog; - private final String productionRunLog; + private int numProductionRuns = 0; public CDSAppTester(String name) { + if (CDSTestUtils.DYNAMIC_DUMP) { + 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"; @@ -53,7 +58,14 @@ abstract public class CDSAppTester { staticArchiveFileLog = staticArchiveFile + ".log"; dynamicArchiveFile = name() + ".dynamic.jsa"; dynamicArchiveFileLog = dynamicArchiveFile + ".log"; - productionRunLog = name() + ".production.log"; + } + + private String productionRunLog() { + if (numProductionRuns == 0) { + return name() + ".production.log"; + } else { + return name() + ".production." + numProductionRuns + ".log"; + } } private enum Workflow { @@ -97,6 +109,11 @@ abstract public class CDSAppTester { public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {} private Workflow workflow; + private boolean checkExitValue = true; + + public final void setCheckExitValue(boolean b) { + checkExitValue = b; + } public final boolean isStaticWorkflow() { return workflow == Workflow.STATIC; @@ -134,7 +151,10 @@ abstract public class CDSAppTester { for (String logFile : logFiles) { listOutputFile(logFile); } - output.shouldHaveExitValue(0); + if (checkExitValue) { + output.shouldHaveExitValue(0); + } + output.shouldNotContain(CDSTestUtils.MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); CDSTestUtils.checkCommonExecExceptions(output); checkExecution(output, runMode); return output; @@ -189,10 +209,22 @@ abstract public class CDSAppTester { } private OutputAnalyzer productionRun() throws Exception { + return productionRun(null, null); + } + + public OutputAnalyzer productionRun(String[] extraVmArgs) throws Exception { + return productionRun(extraVmArgs, null); + } + + // After calling run(String[]), you can call this method to run the app again, with the AOTCache + // using different args to the VM and application. + public OutputAnalyzer productionRun(String[] extraVmArgs, String[] extraAppArgs) throws Exception { RunMode runMode = RunMode.PRODUCTION; String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), + "-XX:+UnlockDiagnosticVMOptions", + "-XX:VerifyArchivedFields=2", // make sure archived heap objects are good. "-cp", classpath(runMode), - logToFile(productionRunLog, "cds")); + logToFile(productionRunLog(), "cds")); if (isStaticWorkflow()) { cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + staticArchiveFile); @@ -200,8 +232,19 @@ abstract public class CDSAppTester { cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + dynamicArchiveFile); } + if (extraVmArgs != null) { + cmdLine = StringArrayUtils.concat(cmdLine, extraVmArgs); + } + cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); - return executeAndCheck(cmdLine, runMode, productionRunLog); + + if (extraAppArgs != null) { + cmdLine = StringArrayUtils.concat(cmdLine, extraAppArgs); + } + + OutputAnalyzer out = executeAndCheck(cmdLine, runMode, productionRunLog()); + numProductionRuns ++; + return out; } public void run(String args[]) throws Exception { diff --git a/test/lib/jdk/test/lib/cds/CDSTestUtils.java b/test/lib/jdk/test/lib/cds/CDSTestUtils.java index 7115912380a..a913f2f9fa2 100644 --- a/test/lib/jdk/test/lib/cds/CDSTestUtils.java +++ b/test/lib/jdk/test/lib/cds/CDSTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, 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,6 +51,8 @@ public class CDSTestUtils { "Unable to allocate region, java heap range is already in use."; public static final String MSG_DYNAMIC_NOT_SUPPORTED = "-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded"; + public static final String MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE = + "an object points to a static field that may hold a different value at runtime"; public static final boolean DYNAMIC_DUMP = Boolean.getBoolean("test.dynamic.cds.archive"); public interface Checker { @@ -284,6 +286,7 @@ public class CDSTestUtils { output.shouldContain("Written dynamic archive 0x"); } output.shouldHaveExitValue(0); + output.shouldNotContain(MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); for (String match : extraMatches) { output.shouldContain(match); @@ -296,6 +299,7 @@ public class CDSTestUtils { public static OutputAnalyzer checkBaseDump(OutputAnalyzer output) throws Exception { output.shouldContain("Loading classes to share"); output.shouldHaveExitValue(0); + output.shouldNotContain(MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); return output; } @@ -859,4 +863,24 @@ public class CDSTestUtils { Files.copy(srcPath, destPath, REPLACE_EXISTING, COPY_ATTRIBUTES); return destPath; } + + // Some tests were initially written without the knowledge of -XX:+AOTClassLinking. These tests need to + // be adjusted if -XX:+AOTClassLinking is specified in jtreg -vmoptions or -javaoptions: + public static boolean isAOTClassLinkingEnabled() { + return isBooleanVMOptionEnabledInCommandLine("AOTClassLinking"); + } + + public static boolean isBooleanVMOptionEnabledInCommandLine(String optionName) { + String lastMatch = null; + String pattern = "^-XX:." + optionName + "$"; + for (String s : Utils.getTestJavaOpts()) { + if (s.matches(pattern)) { + lastMatch = s; + } + } + if (lastMatch != null && lastMatch.equals("-XX:+" + optionName)) { + return true; + } + return false; + } }