8331497: Implement JEP 483: Ahead-of-Time Class Loading & Linking
Reviewed-by: jrose, kvn, heidinga, asmehra, vlivanov
This commit is contained in:
parent
276251c44a
commit
41a2d49f0a
@ -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) \
|
||||
|
359
src/hotspot/share/cds/aotClassInitializer.cpp
Normal file
359
src/hotspot/share/cds/aotClassInitializer.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
46
src/hotspot/share/cds/aotClassInitializer.hpp
Normal file
46
src/hotspot/share/cds/aotClassInitializer.hpp
Normal 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
|
319
src/hotspot/share/cds/aotClassLinker.cpp
Normal file
319
src/hotspot/share/cds/aotClassLinker.cpp
Normal 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";
|
||||
}
|
||||
}
|
||||
|
129
src/hotspot/share/cds/aotClassLinker.hpp
Normal file
129
src/hotspot/share/cds/aotClassLinker.hpp
Normal 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
|
573
src/hotspot/share/cds/aotConstantPoolResolver.cpp
Normal file
573
src/hotspot/share/cds/aotConstantPoolResolver.cpp
Normal 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
|
@ -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
|
397
src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp
Normal file
397
src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp
Normal 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;
|
||||
}
|
||||
}
|
69
src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp
Normal file
69
src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp
Normal 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
|
40
src/hotspot/share/cds/aotLinkedClassTable.cpp
Normal file
40
src/hotspot/share/cds/aotLinkedClassTable.cpp
Normal 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);
|
||||
}
|
||||
|
77
src/hotspot/share/cds/aotLinkedClassTable.hpp
Normal file
77
src/hotspot/share/cds/aotLinkedClassTable.hpp
Normal 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
|
@ -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";
|
||||
num_boot_klasses ++;
|
||||
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";
|
||||
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 {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -97,12 +117,33 @@ public:
|
||||
// --- Archived java objects
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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,12 +190,36 @@ 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().
|
||||
|
||||
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
|
||||
// and check later if it appears in the archived object graph.
|
||||
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -128,11 +128,17 @@ 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
InstanceKlass* super = k->java_super();
|
||||
if (super != nullptr && !has_id(super)) {
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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,6 +1095,11 @@ void FileMapInfo::validate_non_existent_class_paths() {
|
||||
i++) {
|
||||
SharedClassPathEntry* ent = shared_path(i);
|
||||
if (!ent->check_non_existent()) {
|
||||
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);
|
||||
@ -1096,6 +1107,9 @@ void FileMapInfo::validate_non_existent_class_paths() {
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// A utility class for reading/validating the GenericCDSFileMapHeader portion of
|
||||
// a CDS archive's header. The file header of all CDS archives with versions from
|
||||
// CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION (12) are guaranteed to always start
|
||||
@ -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,11 +2598,17 @@ 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.
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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
@ -168,8 +168,14 @@ public:
|
||||
// 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(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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -177,8 +177,12 @@ public:
|
||||
|
||||
InstanceKlass* nest_host() {
|
||||
assert(!ArchiveBuilder::is_active(), "not called when dumping archive");
|
||||
if (_nest_host_offset == 0) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return ArchiveUtils::from_offset<InstanceKlass*>(_nest_host_offset);
|
||||
}
|
||||
}
|
||||
|
||||
RTLoaderConstraint* loader_constraints() {
|
||||
assert(_num_loader_constraints > 0, "sanity");
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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() ||
|
||||
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() ||
|
||||
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)
|
||||
klass->is_subclass_of(vmClasses::Reference_klass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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);
|
||||
@ -159,6 +147,19 @@ void SystemDictionary::compute_java_loaders(TRAPS) {
|
||||
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");
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
oop SystemDictionary::get_system_class_loader_impl(TRAPS) {
|
||||
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
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,8 +601,12 @@ 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()) {
|
||||
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) {
|
||||
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))) {
|
||||
|
@ -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);
|
||||
|
@ -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 ) \
|
||||
|
@ -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
|
||||
|
@ -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") \
|
||||
|
@ -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!
|
||||
|
@ -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);
|
||||
|
@ -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) \
|
||||
|
@ -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();
|
||||
if (cld != nullptr) {
|
||||
ClaimMetadataVisitingOopIterateClosure::do_cld(cld);
|
||||
} else {
|
||||
assert(AOTLinkedClassBulkLoader::is_pending_aot_linked_class(k), "sanity");
|
||||
}
|
||||
}
|
||||
|
||||
inline void ClaimMetadataVisitingOopIterateClosure::do_nmethod(nmethod* nm) {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
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
|
||||
|
@ -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; }
|
||||
|
@ -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;)
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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) \
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
MethodHandle prev = cachedMethodHandle(which);
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
if (USE_SOFT_CACHE) {
|
||||
methodHandles[which] = new SoftReference<>(mh);
|
||||
} else {
|
||||
methodHandles[which] = 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();
|
||||
LambdaForm prev = cachedLambdaForm(which);
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
if (USE_SOFT_CACHE) {
|
||||
lambdaForms[which] = new SoftReference<>(form);
|
||||
} else {
|
||||
lambdaForms[which] = 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:
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
18
test/hotspot/jtreg/ProblemList-AotJdk.txt
Normal file
18
test/hotspot/jtreg/ProblemList-AotJdk.txt
Normal 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
|
@ -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 \
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
203
test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java
Normal file
203
test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java
Normal 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);
|
||||
|
||||
}
|
||||
}
|
@ -190,14 +190,21 @@ 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 -> {
|
||||
"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 {
|
||||
String helloJar = JarBuilder.getOrCreateHelloJar();
|
||||
|
@ -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");
|
||||
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())
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))";
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
@ -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,6 +152,7 @@ public class ArchiveHeapTestClass {
|
||||
output = dumpBootAndHello(CDSTestClassD_name);
|
||||
mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassD::archivedObjects");
|
||||
|
||||
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");
|
||||
@ -158,12 +160,16 @@ public class ArchiveHeapTestClass {
|
||||
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("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 {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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");}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
// 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("cds,resolve.*reverted klass.* ResolvedConstantsApp .*java/lang/System")
|
||||
.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 {
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user