8331497: Implement JEP 483: Ahead-of-Time Class Loading & Linking

Reviewed-by: jrose, kvn, heidinga, asmehra, vlivanov
This commit is contained in:
Ioi Lam 2024-11-15 22:28:54 +00:00
parent 276251c44a
commit 41a2d49f0a
106 changed files with 6601 additions and 760 deletions

View File

@ -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) \

View File

@ -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 <clinit> must be included.
// This ensures that in the production run, we don't run the <clinit> of a supertype but skips
// ik's <clinit>.
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<InstanceKlass*>* 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 <clinit>) 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 <clinit> 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 <clinit> 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 <clinit> again, but instead it simply
// observes that that class was aot-inited. The VM assumes that, if
// it were to run <clinit> 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
// <clinit>. 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 <clinit> 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 <clinit> 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 <clinit> 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.<clinit> explicitly
// initializes Invokers$Holder. Since Invokers.<clinit> 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);
}
}
}

View File

@ -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

View File

@ -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<InstanceKlass*, mtClassShared>* 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<InstanceKlass*, mtClassShared>(1000);
for (auto id : EnumRange<vmClassID>{}) {
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<InstanceKlass*>* 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<InstanceKlass*>* 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<Klass*>* klasses = ArchiveBuilder::current()->klasses();
for (GrowableArrayIterator<Klass*> 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<InstanceKlass*>* AOTClassLinker::write_classes(oop class_loader, bool is_javabase) {
ResourceMark rm;
GrowableArray<InstanceKlass*> 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";
}
}

View File

@ -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 <typename T> 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<InstanceKlass*, bool, TABLE_SIZE, AnyObj::C_HEAP, mtClassShared>;
// 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<InstanceKlass*, mtClassShared>* _sorted_candidates;
DEBUG_ONLY(static bool is_initialized());
static void add_vm_class(InstanceKlass* ik);
static void add_new_candidate(InstanceKlass* ik);
static Array<InstanceKlass*>* 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

View File

@ -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<address>(h_loader()),
cast_from_oop<address>(SystemDictionary::java_system_loader()),
cast_from_oop<address>(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<bool>* 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<bool>* 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<bool>* 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<bool>* 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<ResolvedIndyEntry>* 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;
}
// <k> 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 <clinit> to be executed, then we must delay the execution to the production run
// as <clinit> 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

View File

@ -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 <typename T> 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<InstanceKlass*, bool, 15889, AnyObj::C_HEAP, mtClassShared> ;
class AOTConstantPoolResolver : AllStatic {
static const int TABLE_SIZE = 15889; // prime number
using ClassesTable = ResourceHashtable<InstanceKlass*, bool, TABLE_SIZE, AnyObj::C_HEAP, mtClassShared> ;
static ClassesTable* _processed_classes;
static ClassesTable* _vm_classes;
static void add_one_vm_class(InstanceKlass* ik);
#ifdef ASSERT
template <typename T> 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 <typename T>
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<bool>* 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<bool>* preresolve_list);
static void preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray<bool>* preresolve_list);
static void preresolve_indy_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray<bool>* 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

View File

@ -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 <clinit> 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<InstanceKlass*>* 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 <classes> in the <initiating_loader>. The <classes> should have already been loaded
// by a parent loader of the <initiating_loader>. 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 <initiating_loader>.
void AOTLinkedClassBulkLoader::initiate_loading(JavaThread* current, const char* category_name,
Handle initiating_loader, Array<InstanceKlass*>* 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 <class_loader> 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 <class_loader>.
void AOTLinkedClassBulkLoader::init_required_classes_for_loader(Handle class_loader, Array<InstanceKlass*>* 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;
}
}

View File

@ -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 <typename T> 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<InstanceKlass*>* classes);
static void load_classes_impl(AOTLinkedClassCategory class_category, Array<InstanceKlass*>* 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<InstanceKlass*>* 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

View File

@ -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);
}

View File

@ -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 <typename T> 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<InstanceKlass*>* _boot; // only java.base classes
Array<InstanceKlass*>* _boot2; // boot classes in other modules
Array<InstanceKlass*>* _platform;
Array<InstanceKlass*>* _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<InstanceKlass*>* boot() const { return _boot; }
Array<InstanceKlass*>* boot2() const { return _boot2; }
Array<InstanceKlass*>* platform() const { return _platform; }
Array<InstanceKlass*>* app() const { return _app; }
void set_boot (Array<InstanceKlass*>* value) { _boot = value; }
void set_boot2 (Array<InstanceKlass*>* value) { _boot2 = value; }
void set_platform(Array<InstanceKlass*>* value) { _platform = value; }
void set_app (Array<InstanceKlass*>* value) { _app = value; }
void serialize(SerializeClosure* soc);
};
#endif // SHARE_CDS_AOTLINKEDCLASSTABLE_HPP

View File

@ -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 {

View File

@ -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 <typename T> 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 <typename T> bool has_been_buffered(T src_addr) const {
return has_been_buffered((address)src_addr);
}
address get_buffered_addr(address src_addr) const;
template <typename T> T get_buffered_addr(T src_addr) const {
return (T)get_buffered_addr((address)src_addr);

View File

@ -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;
}
};

View File

@ -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 <typename T> void ArchiveHeapWriter::relocate_field_in_buffer(T* field_addr_in_buffer, CHeapBitMap* oopmap) {
oop source_referent = load_source_oop_from_buffer<T>(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<T>(field_addr_in_buffer, request_referent);
mark_oop_pointer<T>(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);

View File

@ -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;

View File

@ -38,6 +38,9 @@ class BootstrapInfo;
class ReservedSpace;
class VirtualSpace;
template<class E> class Array;
template<class E> 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 <typename T> static Array<T>* archive_array(GrowableArray<T>* tmp_array);
// offset must represent an object of type T in the mapped shared space. Return
// a direct pointer to this object.

View File

@ -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<T> that's allocated in the ArchiveBuilder "buffer" space.
template <typename T>
Array<T>* ArchiveUtils::archive_array(GrowableArray<T>* tmp_array) {
Array<T>* archived_array = ArchiveBuilder::new_ro_array<T>(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<T>::value) {
ArchivePtrMarker::mark_pointer(archived_array->adr_at(i));
}
}
return archived_array;
}
#endif // SHARE_CDS_ARCHIVEUTILS_INLINE_HPP

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <clinit> 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 "<unknown>";
}
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);

View File

@ -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)

View File

@ -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<int>(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);
}
}

View File

@ -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<ResolvedIndyEntry>* 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<ResolvedFieldEntry>* 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);
}
}

View File

@ -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<InstanceKlass*>* 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<vmClassID>{}) {
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<address>(h_loader()),
cast_from_oop<address>(SystemDictionary::java_system_loader()),
cast_from_oop<address>(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<bool>* 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<bool>* 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<bool>* 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

View File

@ -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

View File

@ -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;

View File

@ -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<mtClass> {
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;
};

View File

@ -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");

View File

@ -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;

View File

@ -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();
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <xxx>_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::<xxx>_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<oop, mtClassShared>* _pending_roots;
static GrowableArrayCHeap<OopHandle, mtClassShared>* _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

View File

@ -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();

View File

@ -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<Method*>* 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<OopHandle, mtClassShared>* _extra_interned_strings = nullptr;
// Extra Symbols to be added to the archive
static GrowableArrayCHeap<Symbol*, mtClassShared>* _extra_symbols = nullptr;
// Methods managed by SystemDictionary::find_method_handle_intrinsic() to be added to the archive
static GrowableArray<Method*>* _pending_method_handle_intrinsics = NULL;
void MetaspaceShared::read_extra_data(JavaThread* current, const char* filename) {
_extra_interned_strings = new GrowableArrayCHeap<OopHandle, mtClassShared>(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<Method*>(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<Method*>(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

View File

@ -34,10 +34,12 @@
class ArchiveBuilder;
class ArchiveHeapInfo;
class FileMapInfo;
class Method;
class outputStream;
class SerializeClosure;
class StaticArchiveBuilder;
template<class E> class Array;
template<class E> 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<Method*>* _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<Method*>* 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;

View File

@ -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()) {

View File

@ -177,7 +177,11 @@ public:
InstanceKlass* nest_host() {
assert(!ArchiveBuilder::is_active(), "not called when dumping archive");
return ArchiveUtils::from_offset<InstanceKlass*>(_nest_host_offset);
if (_nest_host_offset == 0) {
return nullptr;
} else {
return ArchiveUtils::from_offset<InstanceKlass*>(_nest_host_offset);
}
}
RTLoaderConstraint* loader_constraints() {

View File

@ -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<s2>(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

View File

@ -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();

View File

@ -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<Klass*>* 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;
}

View File

@ -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; }

View File

@ -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<Method*>* 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<Method*>* 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,

View File

@ -75,7 +75,10 @@ class GCTimer;
class EventClassLoad;
class Symbol;
template <class E> 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<Method*>* 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

View File

@ -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))) {

View File

@ -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);

View File

@ -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 ) \

View File

@ -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

View File

@ -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") \

View File

@ -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!

View File

@ -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);

View File

@ -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) \

View File

@ -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) {

View File

@ -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 <typename Function>
void ConstantPool::iterate_archivable_resolved_references(Function function) {
objArrayOop rr = resolved_references();
if (rr != nullptr && cache() != nullptr && CDSConfig::is_dumping_invokedynamic()) {
Array<ResolvedIndyEntry>* 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<ResolvedMethodEntry>* 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<bool> 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<u2>* 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));
}
}

View File

@ -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 <typename Function> void iterate_archivable_resolved_references(Function function);
#endif
private:

View File

@ -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

View File

@ -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

View File

@ -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<InstanceKlass*>* 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 <clinit>
// - 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 <clinit>",
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 <clinit> 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 <clinit> 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<InstanceKlass*>* 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

View File

@ -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<u2>* permitted_subclasses() const { return _permitted_subclasses; }
void set_permitted_subclasses(Array<u2>* 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; }

View File

@ -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 <clinit>
_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;)

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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=<archive file>
} 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) {

View File

@ -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<ccstr>* constraint = (const JVMTypedFlagLimit<ccstr>*)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<ccstr, EventStringFlagChanged>(flag, old_value, *value, origin);
char* new_value = nullptr;

View File

@ -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,

View File

@ -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) \

View File

@ -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

View File

@ -225,6 +225,11 @@ public final class Class<T> 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<T> 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.

View File

@ -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<LambdaForm> {
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<LambdaForm>(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<LambdaForm>)cache).get();
}
}
}
/**

View File

@ -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"));
}
}

View File

@ -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<MethodType,MethodType> 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<MethodType,MethodType> copyInternTable() {
HashMap<MethodType,MethodType> copy = new HashMap<>();
for (Iterator<MethodType> 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();
}
}

View File

@ -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<MethodHandle>[] 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<LambdaForm>[] 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<MethodHandle> 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<MethodHandle>)entry).get();
}
}
public synchronized MethodHandle setCachedMethodHandle(int which, MethodHandle mh) {
// Simulate a CAS, to avoid racy duplication of results.
SoftReference<MethodHandle> 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<LambdaForm> 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<LambdaForm>)entry).get();
}
}
public synchronized LambdaForm setCachedLambdaForm(int which, LambdaForm form) {
// Simulate a CAS, to avoid racy duplication of results.
SoftReference<LambdaForm> 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:

View File

@ -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";

View File

@ -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<K,V> extends AbstractMap<K,V>
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.

View File

@ -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();
}

View File

@ -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

View File

@ -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 \

View File

@ -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:<output>/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.

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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())

View File

@ -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");
}
}
}

View File

@ -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

View File

@ -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))";

View File

@ -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);
}
}

View File

@ -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.*<no principals>.*java.security.Permissions.*,*java.security.AllPermission.*<all permissions>.*<all actions>";
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 <no signer certificates>",
"jdk.internal.loader.ClassLoaders[$]PlatformClassLoader.*<no principals>.*java.security.Permissions");
check(BulkLoaderTestApp.class,
"jdk.internal.loader.ClassLoaders[$]AppClassLoader@",
"^unnamed module @",
"package ",
"file:.*BulkLoaderTestApp.jar <no signer certificates>",
"jdk.internal.loader.ClassLoaders[$]AppClassLoader.*<no principals>.*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 <no signer certificates>",
"jdk.internal.loader.ClassLoaders[$]AppClassLoader.*<no principals>.*java.security.Permissions");
doit(() -> {
Class<?> lambdaClass = MyUtil.getCallerClass(1);
check(lambdaClass,
"jdk.internal.loader.ClassLoaders[$]AppClassLoader@",
"unnamed module",
"package ",
"file:.*BulkLoaderTestApp.jar <no signer certificates>",
"jdk.internal.loader.ClassLoaders[$]AppClassLoader.*<no principals>.*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<StackFrame> stack = walker.walk(s -> s.limit(depth+2).collect(Collectors.toList()));
return stack.get(depth+1).getDeclaringClass();
}
}

View File

@ -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 "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()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

View File

@ -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.<clinit> 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.<clinit>. 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;
}
}

View File

@ -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())

View File

@ -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);
}
}
}

View File

@ -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,

View File

@ -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.<clinit>");
}
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 <clinit> order.
testClinitOrder();
}
// Check that aot-linking of lambdas does not cause <clinit> 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::<clinit> 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 <clinit> 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<BarA, BarB> 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 <clinit> -- 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 <clinit> -- cannot AOT link the lambda
IC ic = () -> {};
InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC");
ic.doit();
//==============================
// ID1 - has default method, but no <clinit>
// ID2 - has <clinit>, 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 <clinit>
// IE2 - has <clinit>, 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 <clinit>, but no default method
// IF2 - has both default method and <clinit>
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 <clinit>
// IG2 - has both default method and <clinit>
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 <clinit>
// IH2 - has <clinit>, but no default method
// IH3 - has <clinit>, 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 <clinit> method. A lambda implementing this
// interface can be AOT-linked.
@FunctionalInterface
interface IntfWithNoClinit<X extends FooA, Y extends FooB> {
X doit(Y param);
}
// Another interface with no <clinit> 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 <clinit> -- 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 <clinit> -- 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 <clinit>
default int dummy() { return 0; }
void doit();
}
@FunctionalInterface interface ID2 extends ID1 { // has <clinit>, but no default method
static int _dummy = InitTracker.trackEvent("ID2");
}
// (IE1/IE2)
@FunctionalInterface interface IE1 { // has default method and <clinit>
static int _dummy = InitTracker.trackEvent("IE1");
default int dummy() { return _dummy; }
void doit();
}
@FunctionalInterface interface IE2 extends IE1 { // has <clinit>, but no default method
static int _dummy = InitTracker.trackEvent("IE2");
}
// (IF1/IF2)
@FunctionalInterface interface IF1 { // has <clinit>, but no default method
static int _dummy = InitTracker.trackEvent("IF1");
void doit();
}
@FunctionalInterface interface IF2 extends IF1 { // has default method and <clinit>
static int _dummy = InitTracker.trackEvent("IF2");
default int dummy() { return 0; }
}
// (IG1/IG2)
@FunctionalInterface interface IG1 { // has default method and <clinit>
static int _dummy = InitTracker.trackEvent("IG1");
default int dummy() { return _dummy; }
void doit();
}
@FunctionalInterface interface IG2 extends IG1 { // has default method and <clinit>
static int _dummy = InitTracker.trackEvent("IG2");
default int dummy() { return _dummy; }
}
// (IH1/IH2/IH3)
@FunctionalInterface interface IH1 { // has default method and <clinit>
static int _dummy = InitTracker.trackEvent("IH1");
default int dummy() { return _dummy; }
void doit();
}
@FunctionalInterface interface IH2 extends IH1 { // has <clinit> but no default method
static int _dummy = InitTracker.trackEvent("IH2");
}
@FunctionalInterface interface IH3 extends IH2 { // has <clinit> 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 <clinit> 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");}
}

View File

@ -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);
}
}
}

View File

@ -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.<init>:")
.shouldMatch(ALWAYS("method.* ResolvedConstantsApp ResolvedConstantsApp.<init>:"))
// Should resolve references to super constructor
.shouldMatch("cds,resolve.*archived method .* ResolvedConstantsApp java/lang/Object.<init>:")
.shouldMatch(ALWAYS("method.* ResolvedConstantsApp java/lang/Object.<init>:"))
// 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<String> wrapper = oldConsumer::consumeString;
wrapper.accept("Hello");
// Lambda of interfaces that have <clinit> 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 <concat> " + 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 <concat> " + 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 {

View File

@ -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 <concat> " + 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 "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()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."<init>":"()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 <concat> "
};
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

Some files were not shown because too many files have changed in this diff Show More