8354897: Support Soft/Weak Reference in AOT cache
Co-authored-by: Ioi Lam <iklam@openjdk.org> Reviewed-by: liach, eosterlund
This commit is contained in:
parent
9a2a2c5bb1
commit
1ff7e813e3
@ -25,6 +25,7 @@
|
|||||||
#include "cds/aotClassLinker.hpp"
|
#include "cds/aotClassLinker.hpp"
|
||||||
#include "cds/aotArtifactFinder.hpp"
|
#include "cds/aotArtifactFinder.hpp"
|
||||||
#include "cds/aotClassInitializer.hpp"
|
#include "cds/aotClassInitializer.hpp"
|
||||||
|
#include "cds/aotReferenceObjSupport.hpp"
|
||||||
#include "cds/dumpTimeClassInfo.inline.hpp"
|
#include "cds/dumpTimeClassInfo.inline.hpp"
|
||||||
#include "cds/heapShared.hpp"
|
#include "cds/heapShared.hpp"
|
||||||
#include "cds/lambdaProxyClassDictionary.hpp"
|
#include "cds/lambdaProxyClassDictionary.hpp"
|
||||||
@ -73,6 +74,7 @@ void AOTArtifactFinder::find_artifacts() {
|
|||||||
// Note, if a class is not excluded, it does NOT mean it will be automatically included
|
// Note, if a class is not excluded, it does NOT mean it will be automatically included
|
||||||
// into the AOT cache -- that will be decided by the code below.
|
// into the AOT cache -- that will be decided by the code below.
|
||||||
SystemDictionaryShared::finish_exclusion_checks();
|
SystemDictionaryShared::finish_exclusion_checks();
|
||||||
|
AOTReferenceObjSupport::init_keep_alive_objs_table();
|
||||||
|
|
||||||
start_scanning_for_oops();
|
start_scanning_for_oops();
|
||||||
|
|
||||||
|
@ -338,7 +338,8 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
|
|||||||
bool AOTClassInitializer::is_runtime_setup_required(InstanceKlass* ik) {
|
bool AOTClassInitializer::is_runtime_setup_required(InstanceKlass* ik) {
|
||||||
return ik == vmClasses::Class_klass() ||
|
return ik == vmClasses::Class_klass() ||
|
||||||
ik == vmClasses::internal_Unsafe_klass() ||
|
ik == vmClasses::internal_Unsafe_klass() ||
|
||||||
ik == vmClasses::ConcurrentHashMap_klass();
|
ik == vmClasses::ConcurrentHashMap_klass() ||
|
||||||
|
ik == vmClasses::Reference_klass();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AOTClassInitializer::call_runtime_setup(JavaThread* current, InstanceKlass* ik) {
|
void AOTClassInitializer::call_runtime_setup(JavaThread* current, InstanceKlass* ik) {
|
||||||
|
240
src/hotspot/share/cds/aotReferenceObjSupport.cpp
Normal file
240
src/hotspot/share/cds/aotReferenceObjSupport.cpp
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cds/aotReferenceObjSupport.hpp"
|
||||||
|
#include "cds/heapShared.hpp"
|
||||||
|
#include "classfile/javaClasses.hpp"
|
||||||
|
#include "classfile/symbolTable.hpp"
|
||||||
|
#include "classfile/systemDictionary.hpp"
|
||||||
|
#include "classfile/vmSymbols.hpp"
|
||||||
|
#include "logging/log.hpp"
|
||||||
|
#include "memory/resourceArea.hpp"
|
||||||
|
#include "memory/universe.hpp"
|
||||||
|
#include "oops/oop.inline.hpp"
|
||||||
|
#include "oops/oopHandle.inline.hpp"
|
||||||
|
#include "runtime/fieldDescriptor.inline.hpp"
|
||||||
|
#include "runtime/javaCalls.hpp"
|
||||||
|
#include "utilities/resourceHash.hpp"
|
||||||
|
|
||||||
|
// Handling of java.lang.ref.Reference objects in the AOT cache
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// When AOTArtifactFinder finds an oop which is a instance of java.lang.ref.Reference:
|
||||||
|
//
|
||||||
|
// - We check if the oop is eligible to be stored in the AOT cache. If not, the AOT cache
|
||||||
|
// creation fails -- see AOTReferenceObjSupport::check_if_ref_obj()
|
||||||
|
//
|
||||||
|
// - Otherwise, we store the oop into the AOT cache, but we unconditionally reset its
|
||||||
|
// "next" and "discovered" fields to null. Otherwise, if AOTArtifactFinder follows these
|
||||||
|
// fields, it may found unrelated objects that we don't intend to cache.
|
||||||
|
//
|
||||||
|
// Eligibility
|
||||||
|
// ===========
|
||||||
|
//
|
||||||
|
// [1] A reference that does not require special clean up (i.e., Reference::queue == ReferenceQueue.NULL_QUEUE)
|
||||||
|
// is eligible.
|
||||||
|
//
|
||||||
|
// [2] A reference that REQUIRE specials clean up (i.e., Reference::queue != ReferenceQueue.NULL_QUEUE)
|
||||||
|
// is eligible ONLY if its referent is not null.
|
||||||
|
//
|
||||||
|
// As of this version, the only oops in group [2] that can be found by AOTArtifactFinder are
|
||||||
|
// the keys used by ReferencedKeyMap in the implementation of MethodType::internTable.
|
||||||
|
// stabilize_cached_reference_objects() ensures that all keys found by AOTArtifactFinder are eligible.
|
||||||
|
//
|
||||||
|
// The purpose of the error check in check_if_ref_obj() is to guard against changes in the JDK core
|
||||||
|
// libs that might introduce new types of oops in group [2] into the AOT cache.
|
||||||
|
//
|
||||||
|
// Reasons for the eligibility restrictions
|
||||||
|
// ========================================
|
||||||
|
//
|
||||||
|
// Reference handling is complex. In this version, we implement only enough functionality to support
|
||||||
|
// the use of Weak/Soft references used by java.lang.invoke.
|
||||||
|
//
|
||||||
|
// We intend to evolve the implementation in the future by
|
||||||
|
// -- implementing more assemblySetup() operations for other use cases, and/or
|
||||||
|
// -- relaxing the eligibility restrictions.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// null referents for group [1]
|
||||||
|
// ============================
|
||||||
|
//
|
||||||
|
// Any cached reference R1 of group [1] is allowed to have a null referent.
|
||||||
|
// This can happen in the following situations:
|
||||||
|
// (a) R1.clear() was called by Java code during the assembly phase.
|
||||||
|
// (b) The referent has been collected, and R1 is in the "pending" state.
|
||||||
|
// In case (b), the "next" and "discovered" fields of the cached copy of R1 will
|
||||||
|
// be set to null. During the production run:
|
||||||
|
// - It would appear to the Java program as if immediately during VM start-up, the referent
|
||||||
|
// was collected and ReferenceThread completed processing of R1.
|
||||||
|
// - It would appear to the GC as if immediately during VM start-up, the Java program called
|
||||||
|
// R1.clear().
|
||||||
|
|
||||||
|
#if INCLUDE_CDS_JAVA_HEAP
|
||||||
|
|
||||||
|
class KeepAliveObjectsTable : public ResourceHashtable<oop, bool,
|
||||||
|
36137, // prime number
|
||||||
|
AnyObj::C_HEAP,
|
||||||
|
mtClassShared,
|
||||||
|
HeapShared::oop_hash> {};
|
||||||
|
|
||||||
|
static KeepAliveObjectsTable* _keep_alive_objs_table;
|
||||||
|
static OopHandle _keep_alive_objs_array;
|
||||||
|
static OopHandle _null_queue;
|
||||||
|
|
||||||
|
bool AOTReferenceObjSupport::is_enabled() {
|
||||||
|
// For simplicity, AOTReferenceObjSupport is enabled only when dumping method handles.
|
||||||
|
// Otherwise we won't see Reference objects in the AOT cache. Let's be conservative now.
|
||||||
|
return CDSConfig::is_dumping_method_handles();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AOTReferenceObjSupport::initialize(TRAPS) {
|
||||||
|
if (!AOTReferenceObjSupport::is_enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TempNewSymbol class_name = SymbolTable::new_symbol("java/lang/ref/ReferenceQueue");
|
||||||
|
Klass* k = SystemDictionary::resolve_or_fail(class_name, true, CHECK);
|
||||||
|
InstanceKlass* ik = InstanceKlass::cast(k);
|
||||||
|
ik->initialize(CHECK);
|
||||||
|
|
||||||
|
TempNewSymbol field_name = SymbolTable::new_symbol("NULL_QUEUE");
|
||||||
|
fieldDescriptor fd;
|
||||||
|
bool found = ik->find_local_field(field_name, vmSymbols::referencequeue_signature(), &fd);
|
||||||
|
precond(found);
|
||||||
|
precond(fd.is_static());
|
||||||
|
|
||||||
|
_null_queue = OopHandle(Universe::vm_global(), ik->java_mirror()->obj_field(fd.offset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that all group [2] references found by AOTArtifactFinder are eligible.
|
||||||
|
void AOTReferenceObjSupport::stabilize_cached_reference_objects(TRAPS) {
|
||||||
|
if (AOTReferenceObjSupport::is_enabled()) {
|
||||||
|
// This assert means that the MethodType and MethodTypeForm tables won't be
|
||||||
|
// updated concurrently, so we can remove GC'ed entries ...
|
||||||
|
assert(CDSConfig::allow_only_single_java_thread(), "Required");
|
||||||
|
|
||||||
|
{
|
||||||
|
TempNewSymbol method_name = SymbolTable::new_symbol("assemblySetup");
|
||||||
|
JavaValue result(T_VOID);
|
||||||
|
JavaCalls::call_static(&result, vmClasses::MethodType_klass(),
|
||||||
|
method_name,
|
||||||
|
vmSymbols::void_method_signature(),
|
||||||
|
CHECK);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Symbol* cds_name = vmSymbols::jdk_internal_misc_CDS();
|
||||||
|
Klass* cds_klass = SystemDictionary::resolve_or_fail(cds_name, true /*throw error*/, CHECK);
|
||||||
|
TempNewSymbol method_name = SymbolTable::new_symbol("getKeepAliveObjects");
|
||||||
|
TempNewSymbol method_sig = SymbolTable::new_symbol("()[Ljava/lang/Object;");
|
||||||
|
JavaValue result(T_OBJECT);
|
||||||
|
JavaCalls::call_static(&result, cds_klass, method_name, method_sig, CHECK);
|
||||||
|
|
||||||
|
_keep_alive_objs_array = OopHandle(Universe::vm_global(), result.get_oop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AOTReferenceObjSupport::init_keep_alive_objs_table() {
|
||||||
|
assert_at_safepoint(); // _keep_alive_objs_table uses raw oops
|
||||||
|
oop a = _keep_alive_objs_array.resolve();
|
||||||
|
if (a != nullptr) {
|
||||||
|
precond(a->is_objArray());
|
||||||
|
precond(AOTReferenceObjSupport::is_enabled());
|
||||||
|
objArrayOop array = objArrayOop(a);
|
||||||
|
|
||||||
|
_keep_alive_objs_table = new (mtClass)KeepAliveObjectsTable();
|
||||||
|
for (int i = 0; i < array->length(); i++) {
|
||||||
|
oop obj = array->obj_at(i);
|
||||||
|
_keep_alive_objs_table->put(obj, true); // The array may have duplicated entries but that's OK.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true IFF obj is an instance of java.lang.ref.Reference. If so, perform extra eligibility checks.
|
||||||
|
bool AOTReferenceObjSupport::check_if_ref_obj(oop obj) {
|
||||||
|
// We have a single Java thread. This means java.lang.ref.Reference$ReferenceHandler thread
|
||||||
|
// is not running. Otherwise the checks for next/discovered may not work.
|
||||||
|
precond(CDSConfig::allow_only_single_java_thread());
|
||||||
|
assert_at_safepoint(); // _keep_alive_objs_table uses raw oops
|
||||||
|
|
||||||
|
if (obj->klass()->is_subclass_of(vmClasses::Reference_klass())) {
|
||||||
|
precond(AOTReferenceObjSupport::is_enabled());
|
||||||
|
precond(JavaClasses::is_supported_for_archiving(obj));
|
||||||
|
precond(_keep_alive_objs_table != nullptr);
|
||||||
|
|
||||||
|
// GC needs to know about this load, It will keep referent alive until the current safepoint ends.
|
||||||
|
oop referent = HeapAccess<ON_UNKNOWN_OOP_REF>::oop_load_at(obj, java_lang_ref_Reference::referent_offset());
|
||||||
|
|
||||||
|
oop queue = obj->obj_field(java_lang_ref_Reference::queue_offset());
|
||||||
|
oop next = java_lang_ref_Reference::next(obj);
|
||||||
|
oop discovered = java_lang_ref_Reference::discovered(obj);
|
||||||
|
bool needs_special_cleanup = (queue != _null_queue.resolve());
|
||||||
|
|
||||||
|
// If you see the errors below, you probably modified the implementation of java.lang.invoke.
|
||||||
|
// Please check the comments at the top of this file.
|
||||||
|
if (needs_special_cleanup && (referent == nullptr || !_keep_alive_objs_table->contains(referent))) {
|
||||||
|
ResourceMark rm;
|
||||||
|
|
||||||
|
log_error(cds, heap)("Cannot archive reference object " PTR_FORMAT " of class %s",
|
||||||
|
p2i(obj), obj->klass()->external_name());
|
||||||
|
log_error(cds, heap)("referent = " PTR_FORMAT
|
||||||
|
", queue = " PTR_FORMAT
|
||||||
|
", next = " PTR_FORMAT
|
||||||
|
", discovered = " PTR_FORMAT,
|
||||||
|
p2i(referent), p2i(queue), p2i(next), p2i(discovered));
|
||||||
|
log_error(cds, heap)("This object requires special clean up as its queue is not ReferenceQueue::N" "ULL ("
|
||||||
|
PTR_FORMAT ")", p2i(_null_queue.resolve()));
|
||||||
|
log_error(cds, heap)("%s", (referent == nullptr) ?
|
||||||
|
"referent cannot be null" : "referent is not registered with CDS.keepAlive()");
|
||||||
|
HeapShared::debug_trace();
|
||||||
|
MetaspaceShared::unrecoverable_writing_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log_is_enabled(Info, cds, ref)) {
|
||||||
|
ResourceMark rm;
|
||||||
|
log_info(cds, ref)("Reference obj:"
|
||||||
|
" r=" PTR_FORMAT
|
||||||
|
" q=" PTR_FORMAT
|
||||||
|
" n=" PTR_FORMAT
|
||||||
|
" d=" PTR_FORMAT
|
||||||
|
" %s",
|
||||||
|
p2i(referent),
|
||||||
|
p2i(queue),
|
||||||
|
p2i(next),
|
||||||
|
p2i(discovered),
|
||||||
|
obj->klass()->external_name());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AOTReferenceObjSupport::skip_field(int field_offset) {
|
||||||
|
return (field_offset == java_lang_ref_Reference::next_offset() ||
|
||||||
|
field_offset == java_lang_ref_Reference::discovered_offset());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // INCLUDE_CDS_JAVA_HEAP
|
45
src/hotspot/share/cds/aotReferenceObjSupport.hpp
Normal file
45
src/hotspot/share/cds/aotReferenceObjSupport.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SHARE_CDS_AOTREFERENCEOBJSUPPORT_HPP
|
||||||
|
#define SHARE_CDS_AOTREFERENCEOBJSUPPORT_HPP
|
||||||
|
|
||||||
|
#include "memory/allStatic.hpp"
|
||||||
|
#include "oops/oopsHierarchy.hpp"
|
||||||
|
#include "utilities/exceptions.hpp"
|
||||||
|
|
||||||
|
// Support for ahead-of-time allocated instances of java.lang.ref.Reference
|
||||||
|
|
||||||
|
class AOTReferenceObjSupport : AllStatic {
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void initialize(TRAPS);
|
||||||
|
static void stabilize_cached_reference_objects(TRAPS);
|
||||||
|
static void init_keep_alive_objs_table() NOT_CDS_JAVA_HEAP_RETURN;
|
||||||
|
static bool check_if_ref_obj(oop obj);
|
||||||
|
static bool skip_field(int field_offset);
|
||||||
|
static bool is_enabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SHARE_CDS_AOTREFERENCEOBJSUPPORT_HPP
|
@ -22,6 +22,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "cds/aotReferenceObjSupport.hpp"
|
||||||
#include "cds/archiveHeapWriter.hpp"
|
#include "cds/archiveHeapWriter.hpp"
|
||||||
#include "cds/cdsConfig.hpp"
|
#include "cds/cdsConfig.hpp"
|
||||||
#include "cds/filemap.hpp"
|
#include "cds/filemap.hpp"
|
||||||
@ -607,18 +608,27 @@ class ArchiveHeapWriter::EmbeddedOopRelocator: public BasicOopIterateClosure {
|
|||||||
oop _src_obj;
|
oop _src_obj;
|
||||||
address _buffered_obj;
|
address _buffered_obj;
|
||||||
CHeapBitMap* _oopmap;
|
CHeapBitMap* _oopmap;
|
||||||
|
bool _is_java_lang_ref;
|
||||||
public:
|
public:
|
||||||
EmbeddedOopRelocator(oop src_obj, address buffered_obj, CHeapBitMap* oopmap) :
|
EmbeddedOopRelocator(oop src_obj, address buffered_obj, CHeapBitMap* oopmap) :
|
||||||
_src_obj(src_obj), _buffered_obj(buffered_obj), _oopmap(oopmap) {}
|
_src_obj(src_obj), _buffered_obj(buffered_obj), _oopmap(oopmap)
|
||||||
|
{
|
||||||
|
_is_java_lang_ref = AOTReferenceObjSupport::check_if_ref_obj(src_obj);
|
||||||
|
}
|
||||||
|
|
||||||
void do_oop(narrowOop *p) { EmbeddedOopRelocator::do_oop_work(p); }
|
void do_oop(narrowOop *p) { EmbeddedOopRelocator::do_oop_work(p); }
|
||||||
void do_oop( oop *p) { EmbeddedOopRelocator::do_oop_work(p); }
|
void do_oop( oop *p) { EmbeddedOopRelocator::do_oop_work(p); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <class T> void do_oop_work(T *p) {
|
template <class T> void do_oop_work(T *p) {
|
||||||
size_t field_offset = pointer_delta(p, _src_obj, sizeof(char));
|
int field_offset = pointer_delta_as_int((char*)p, cast_from_oop<char*>(_src_obj));
|
||||||
ArchiveHeapWriter::relocate_field_in_buffer<T>((T*)(_buffered_obj + field_offset), _oopmap);
|
T* field_addr = (T*)(_buffered_obj + field_offset);
|
||||||
|
if (_is_java_lang_ref && AOTReferenceObjSupport::skip_field(field_offset)) {
|
||||||
|
// Do not copy these fields. Set them to null
|
||||||
|
*field_addr = (T)0x0;
|
||||||
|
} else {
|
||||||
|
ArchiveHeapWriter::relocate_field_in_buffer<T>(field_addr, _oopmap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -536,9 +536,6 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla
|
|||||||
// run to another which resulting in non-determinstic CDS archives.
|
// run to another which resulting in non-determinstic CDS archives.
|
||||||
// Disable UseStringDeduplication while dumping CDS archive.
|
// Disable UseStringDeduplication while dumping CDS archive.
|
||||||
UseStringDeduplication = false;
|
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
|
// RecordDynamicDumpInfo is not compatible with ArchiveClassesAtExit
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "cds/aotArtifactFinder.hpp"
|
#include "cds/aotArtifactFinder.hpp"
|
||||||
#include "cds/aotClassInitializer.hpp"
|
#include "cds/aotClassInitializer.hpp"
|
||||||
#include "cds/aotClassLocation.hpp"
|
#include "cds/aotClassLocation.hpp"
|
||||||
|
#include "cds/aotReferenceObjSupport.hpp"
|
||||||
#include "cds/archiveBuilder.hpp"
|
#include "cds/archiveBuilder.hpp"
|
||||||
#include "cds/archiveHeapLoader.hpp"
|
#include "cds/archiveHeapLoader.hpp"
|
||||||
#include "cds/archiveHeapWriter.hpp"
|
#include "cds/archiveHeapWriter.hpp"
|
||||||
@ -1363,34 +1364,37 @@ void HeapShared::clear_archived_roots_of(Klass* k) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push all oops that are referenced by _referencing_obj onto the _stack.
|
// Push all oop fields (or oop array elemenets in case of an objArray) in
|
||||||
class HeapShared::ReferentPusher: public BasicOopIterateClosure {
|
// _referencing_obj onto the _stack.
|
||||||
|
class HeapShared::OopFieldPusher: public BasicOopIterateClosure {
|
||||||
PendingOopStack* _stack;
|
PendingOopStack* _stack;
|
||||||
GrowableArray<oop> _found_oop_fields;
|
GrowableArray<oop> _found_oop_fields;
|
||||||
int _level;
|
int _level;
|
||||||
bool _record_klasses_only;
|
bool _record_klasses_only;
|
||||||
KlassSubGraphInfo* _subgraph_info;
|
KlassSubGraphInfo* _subgraph_info;
|
||||||
oop _referencing_obj;
|
oop _referencing_obj;
|
||||||
|
bool _is_java_lang_ref;
|
||||||
public:
|
public:
|
||||||
ReferentPusher(PendingOopStack* stack,
|
OopFieldPusher(PendingOopStack* stack,
|
||||||
int level,
|
int level,
|
||||||
bool record_klasses_only,
|
bool record_klasses_only,
|
||||||
KlassSubGraphInfo* subgraph_info,
|
KlassSubGraphInfo* subgraph_info,
|
||||||
oop orig) :
|
oop orig) :
|
||||||
_stack(stack),
|
_stack(stack),
|
||||||
_found_oop_fields(),
|
_found_oop_fields(),
|
||||||
_level(level),
|
_level(level),
|
||||||
_record_klasses_only(record_klasses_only),
|
_record_klasses_only(record_klasses_only),
|
||||||
_subgraph_info(subgraph_info),
|
_subgraph_info(subgraph_info),
|
||||||
_referencing_obj(orig) {
|
_referencing_obj(orig) {
|
||||||
|
_is_java_lang_ref = AOTReferenceObjSupport::check_if_ref_obj(orig);
|
||||||
}
|
}
|
||||||
void do_oop(narrowOop *p) { ReferentPusher::do_oop_work(p); }
|
void do_oop(narrowOop *p) { OopFieldPusher::do_oop_work(p); }
|
||||||
void do_oop( oop *p) { ReferentPusher::do_oop_work(p); }
|
void do_oop( oop *p) { OopFieldPusher::do_oop_work(p); }
|
||||||
|
|
||||||
~ReferentPusher() {
|
~OopFieldPusher() {
|
||||||
while (_found_oop_fields.length() > 0) {
|
while (_found_oop_fields.length() > 0) {
|
||||||
// This produces the exact same traversal order as the previous version
|
// This produces the exact same traversal order as the previous version
|
||||||
// of ReferentPusher that recurses on the C stack -- a depth-first search,
|
// of OopFieldPusher that recurses on the C stack -- a depth-first search,
|
||||||
// walking the oop fields in _referencing_obj by ascending field offsets.
|
// walking the oop fields in _referencing_obj by ascending field offsets.
|
||||||
oop obj = _found_oop_fields.pop();
|
oop obj = _found_oop_fields.pop();
|
||||||
_stack->push(PendingOop(obj, _referencing_obj, _level + 1));
|
_stack->push(PendingOop(obj, _referencing_obj, _level + 1));
|
||||||
@ -1399,14 +1403,18 @@ class HeapShared::ReferentPusher: public BasicOopIterateClosure {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
template <class T> void do_oop_work(T *p) {
|
template <class T> void do_oop_work(T *p) {
|
||||||
oop obj = RawAccess<>::oop_load(p);
|
int field_offset = pointer_delta_as_int((char*)p, cast_from_oop<char*>(_referencing_obj));
|
||||||
|
oop obj = HeapAccess<ON_UNKNOWN_OOP_REF>::oop_load_at(_referencing_obj, field_offset);
|
||||||
if (!CompressedOops::is_null(obj)) {
|
if (!CompressedOops::is_null(obj)) {
|
||||||
size_t field_delta = pointer_delta(p, _referencing_obj, sizeof(char));
|
if (_is_java_lang_ref && AOTReferenceObjSupport::skip_field(field_offset)) {
|
||||||
|
// Do not follow these fields. They will be cleared to null.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_record_klasses_only && log_is_enabled(Debug, cds, heap)) {
|
if (!_record_klasses_only && log_is_enabled(Debug, cds, heap)) {
|
||||||
ResourceMark rm;
|
ResourceMark rm;
|
||||||
log_debug(cds, heap)("(%d) %s[%zu] ==> " PTR_FORMAT " size %zu %s", _level,
|
log_debug(cds, heap)("(%d) %s[%d] ==> " PTR_FORMAT " size %zu %s", _level,
|
||||||
_referencing_obj->klass()->external_name(), field_delta,
|
_referencing_obj->klass()->external_name(), field_offset,
|
||||||
p2i(obj), obj->size() * HeapWordSize, obj->klass()->external_name());
|
p2i(obj), obj->size() * HeapWordSize, obj->klass()->external_name());
|
||||||
if (log_is_enabled(Trace, cds, heap)) {
|
if (log_is_enabled(Trace, cds, heap)) {
|
||||||
LogTarget(Trace, cds, heap) log;
|
LogTarget(Trace, cds, heap) log;
|
||||||
@ -1586,7 +1594,7 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap
|
|||||||
// Find all the oops that are referenced by orig_obj, push them onto the stack
|
// Find all the oops that are referenced by orig_obj, push them onto the stack
|
||||||
// so we can work on them next.
|
// so we can work on them next.
|
||||||
ResourceMark rm;
|
ResourceMark rm;
|
||||||
ReferentPusher pusher(stack, level, record_klasses_only, subgraph_info, orig_obj);
|
OopFieldPusher pusher(stack, level, record_klasses_only, subgraph_info, orig_obj);
|
||||||
orig_obj->oop_iterate(&pusher);
|
orig_obj->oop_iterate(&pusher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1613,7 +1621,7 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap
|
|||||||
// - No java.lang.Class instance (java mirror) can be included inside
|
// - No java.lang.Class instance (java mirror) can be included inside
|
||||||
// an archived sub-graph. Mirror can only be the sub-graph entry object.
|
// an archived sub-graph. Mirror can only be the sub-graph entry object.
|
||||||
//
|
//
|
||||||
// The Java heap object sub-graph archiving process (see ReferentPusher):
|
// The Java heap object sub-graph archiving process (see OopFieldPusher):
|
||||||
//
|
//
|
||||||
// 1) Java object sub-graph archiving starts from a given static field
|
// 1) Java object sub-graph archiving starts from a given static field
|
||||||
// within a Class instance (java mirror). If the static field is a
|
// within a Class instance (java mirror). If the static field is a
|
||||||
|
@ -164,8 +164,8 @@ private:
|
|||||||
|
|
||||||
static void count_allocation(size_t size);
|
static void count_allocation(size_t size);
|
||||||
static void print_stats();
|
static void print_stats();
|
||||||
static void debug_trace();
|
|
||||||
public:
|
public:
|
||||||
|
static void debug_trace();
|
||||||
static unsigned oop_hash(oop const& p);
|
static unsigned oop_hash(oop const& p);
|
||||||
static unsigned string_oop_hash(oop const& string) {
|
static unsigned string_oop_hash(oop const& string) {
|
||||||
return java_lang_String::hash_code(string);
|
return java_lang_String::hash_code(string);
|
||||||
@ -357,7 +357,7 @@ private:
|
|||||||
int level() const { return _level; }
|
int level() const { return _level; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class ReferentPusher;
|
class OopFieldPusher;
|
||||||
using PendingOopStack = GrowableArrayCHeap<PendingOop, mtClassShared>;
|
using PendingOopStack = GrowableArrayCHeap<PendingOop, mtClassShared>;
|
||||||
|
|
||||||
static PendingOop _object_being_archived;
|
static PendingOop _object_being_archived;
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "cds/aotClassLocation.hpp"
|
#include "cds/aotClassLocation.hpp"
|
||||||
#include "cds/aotConstantPoolResolver.hpp"
|
#include "cds/aotConstantPoolResolver.hpp"
|
||||||
#include "cds/aotLinkedClassBulkLoader.hpp"
|
#include "cds/aotLinkedClassBulkLoader.hpp"
|
||||||
|
#include "cds/aotReferenceObjSupport.hpp"
|
||||||
#include "cds/archiveBuilder.hpp"
|
#include "cds/archiveBuilder.hpp"
|
||||||
#include "cds/archiveHeapLoader.hpp"
|
#include "cds/archiveHeapLoader.hpp"
|
||||||
#include "cds/archiveHeapWriter.hpp"
|
#include "cds/archiveHeapWriter.hpp"
|
||||||
@ -962,22 +963,14 @@ void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS
|
|||||||
#if INCLUDE_CDS_JAVA_HEAP
|
#if INCLUDE_CDS_JAVA_HEAP
|
||||||
if (CDSConfig::is_dumping_heap()) {
|
if (CDSConfig::is_dumping_heap()) {
|
||||||
ArchiveHeapWriter::init();
|
ArchiveHeapWriter::init();
|
||||||
|
|
||||||
if (CDSConfig::is_dumping_full_module_graph()) {
|
if (CDSConfig::is_dumping_full_module_graph()) {
|
||||||
ClassLoaderDataShared::ensure_module_entry_tables_exist();
|
ClassLoaderDataShared::ensure_module_entry_tables_exist();
|
||||||
HeapShared::reset_archived_object_states(CHECK);
|
HeapShared::reset_archived_object_states(CHECK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CDSConfig::is_dumping_method_handles()) {
|
AOTReferenceObjSupport::initialize(CHECK);
|
||||||
// This assert means that the MethodType and MethodTypeForm tables won't be
|
AOTReferenceObjSupport::stabilize_cached_reference_objects(CHECK);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CDSConfig::is_initing_classes_at_dump_time()) {
|
if (CDSConfig::is_initing_classes_at_dump_time()) {
|
||||||
// java.lang.Class::reflectionFactory cannot be archived yet. We set this field
|
// java.lang.Class::reflectionFactory cannot be archived yet. We set this field
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "cds/aotReferenceObjSupport.hpp"
|
||||||
#include "cds/archiveBuilder.hpp"
|
#include "cds/archiveBuilder.hpp"
|
||||||
#include "cds/archiveHeapLoader.hpp"
|
#include "cds/archiveHeapLoader.hpp"
|
||||||
#include "cds/cdsConfig.hpp"
|
#include "cds/cdsConfig.hpp"
|
||||||
@ -5460,9 +5461,7 @@ bool JavaClasses::is_supported_for_archiving(oop obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (klass->is_subclass_of(vmClasses::Reference_klass())) {
|
if (!AOTReferenceObjSupport::is_enabled() && klass->is_subclass_of(vmClasses::Reference_klass())) {
|
||||||
// It's problematic to archive Reference objects. One of the reasons is that
|
|
||||||
// Reference::discovered may pull in unwanted objects (see JDK-8284336)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,7 +719,6 @@ class SerializeClosure;
|
|||||||
JFR_TEMPLATES(template) \
|
JFR_TEMPLATES(template) \
|
||||||
\
|
\
|
||||||
/* CDS */ \
|
/* CDS */ \
|
||||||
template(createArchivedObjects, "createArchivedObjects") \
|
|
||||||
template(dumpSharedArchive, "dumpSharedArchive") \
|
template(dumpSharedArchive, "dumpSharedArchive") \
|
||||||
template(dumpSharedArchive_signature, "(ZLjava/lang/String;)Ljava/lang/String;") \
|
template(dumpSharedArchive_signature, "(ZLjava/lang/String;)Ljava/lang/String;") \
|
||||||
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
|
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \
|
||||||
|
@ -38,7 +38,6 @@ import static java.lang.invoke.LambdaForm.BasicType.*;
|
|||||||
import static java.lang.invoke.MethodHandleImpl.Intrinsic;
|
import static java.lang.invoke.MethodHandleImpl.Intrinsic;
|
||||||
import static java.lang.invoke.MethodHandleImpl.NF_loop;
|
import static java.lang.invoke.MethodHandleImpl.NF_loop;
|
||||||
import static java.lang.invoke.MethodHandleImpl.makeIntrinsic;
|
import static java.lang.invoke.MethodHandleImpl.makeIntrinsic;
|
||||||
import static java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE;
|
|
||||||
|
|
||||||
/** Transforms on LFs.
|
/** Transforms on LFs.
|
||||||
* A lambda-form editor can derive new LFs from its base LF.
|
* A lambda-form editor can derive new LFs from its base LF.
|
||||||
@ -90,17 +89,12 @@ class LambdaFormEditor {
|
|||||||
* Tightly coupled with the TransformKey class, which is used to lookup existing
|
* Tightly coupled with the TransformKey class, which is used to lookup existing
|
||||||
* Transforms.
|
* Transforms.
|
||||||
*/
|
*/
|
||||||
private static final class Transform {
|
private static final class Transform extends SoftReference<LambdaForm> {
|
||||||
final Object cache;
|
|
||||||
final long packedBytes;
|
final long packedBytes;
|
||||||
final byte[] fullBytes;
|
final byte[] fullBytes;
|
||||||
|
|
||||||
private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) {
|
private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) {
|
||||||
if (USE_SOFT_CACHE) {
|
super(result);
|
||||||
cache = new SoftReference<LambdaForm>(result);
|
|
||||||
} else {
|
|
||||||
cache = result;
|
|
||||||
}
|
|
||||||
this.packedBytes = packedBytes;
|
this.packedBytes = packedBytes;
|
||||||
this.fullBytes = fullBytes;
|
this.fullBytes = fullBytes;
|
||||||
}
|
}
|
||||||
@ -141,15 +135,6 @@ class LambdaFormEditor {
|
|||||||
}
|
}
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public LambdaForm get() {
|
|
||||||
if (cache instanceof LambdaForm lf) {
|
|
||||||
return lf;
|
|
||||||
} else {
|
|
||||||
return ((SoftReference<LambdaForm>)cache).get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -665,22 +665,4 @@ class MethodHandleNatives {
|
|||||||
return (definingClass.isAssignableFrom(symbolicRefClass) || // Msym overrides Mdef
|
return (definingClass.isAssignableFrom(symbolicRefClass) || // Msym overrides Mdef
|
||||||
symbolicRefClass.isInterface()); // Mdef implements Msym
|
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 {
|
|
||||||
USE_SOFT_CACHE = Boolean.parseBoolean(
|
|
||||||
System.getProperty("java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE", "true"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -31,8 +31,6 @@ import java.lang.constant.MethodTypeDesc;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -42,7 +40,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
import jdk.internal.util.ReferencedKeySet;
|
import jdk.internal.util.ReferencedKeySet;
|
||||||
import jdk.internal.util.ReferenceKey;
|
import jdk.internal.util.ReferenceKey;
|
||||||
import jdk.internal.misc.CDS;
|
|
||||||
import jdk.internal.vm.annotation.Stable;
|
import jdk.internal.vm.annotation.Stable;
|
||||||
import sun.invoke.util.BytecodeDescriptor;
|
import sun.invoke.util.BytecodeDescriptor;
|
||||||
import sun.invoke.util.VerifyType;
|
import sun.invoke.util.VerifyType;
|
||||||
@ -394,17 +391,6 @@ class MethodType
|
|||||||
ptypes = NO_PTYPES; trusted = true;
|
ptypes = NO_PTYPES; trusted = true;
|
||||||
}
|
}
|
||||||
MethodType primordialMT = new MethodType(rtype, ptypes);
|
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);
|
MethodType mt = internTable.get(primordialMT);
|
||||||
if (mt != null)
|
if (mt != null)
|
||||||
return mt;
|
return mt;
|
||||||
@ -425,7 +411,6 @@ class MethodType
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final @Stable MethodType[] objectOnlyTypes = new MethodType[20];
|
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.
|
* Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array.
|
||||||
@ -1397,29 +1382,9 @@ s.writeObject(this.parameterArray());
|
|||||||
return mt;
|
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
|
// This is called from C code, at the very end of Java code execution
|
||||||
// during the AOT cache assembly phase.
|
// during the AOT cache assembly phase.
|
||||||
static void createArchivedObjects() {
|
private static void assemblySetup() {
|
||||||
// After the archivedMethodTypes field is assigned, this table
|
internTable.prepareForAOTCache();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ import sun.invoke.util.Wrapper;
|
|||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
|
|
||||||
import static java.lang.invoke.MethodHandleStatics.newIllegalArgumentException;
|
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
|
* Shared information for a group of method types, which differ
|
||||||
@ -52,7 +51,7 @@ final class MethodTypeForm {
|
|||||||
final MethodType basicType; // the canonical erasure, with primitives simplified
|
final MethodType basicType; // the canonical erasure, with primitives simplified
|
||||||
|
|
||||||
// Cached adapter information:
|
// Cached adapter information:
|
||||||
private final Object[] methodHandles;
|
private final SoftReference<MethodHandle>[] methodHandles;
|
||||||
|
|
||||||
// Indexes into methodHandles:
|
// Indexes into methodHandles:
|
||||||
static final int
|
static final int
|
||||||
@ -62,7 +61,7 @@ final class MethodTypeForm {
|
|||||||
MH_LIMIT = 3;
|
MH_LIMIT = 3;
|
||||||
|
|
||||||
// Cached lambda form information, for basic types only:
|
// Cached lambda form information, for basic types only:
|
||||||
private final Object[] lambdaForms;
|
private final SoftReference<LambdaForm>[] lambdaForms;
|
||||||
|
|
||||||
private SoftReference<MemberName> interpretEntry;
|
private SoftReference<MemberName> interpretEntry;
|
||||||
|
|
||||||
@ -111,16 +110,9 @@ final class MethodTypeForm {
|
|||||||
return basicType;
|
return basicType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public MethodHandle cachedMethodHandle(int which) {
|
public MethodHandle cachedMethodHandle(int which) {
|
||||||
Object entry = methodHandles[which];
|
SoftReference<MethodHandle> entry = methodHandles[which];
|
||||||
if (entry == null) {
|
return (entry != null) ? entry.get() : 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) {
|
public synchronized MethodHandle setCachedMethodHandle(int which, MethodHandle mh) {
|
||||||
@ -129,24 +121,13 @@ final class MethodTypeForm {
|
|||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
if (USE_SOFT_CACHE) {
|
methodHandles[which] = new SoftReference<>(mh);
|
||||||
methodHandles[which] = new SoftReference<>(mh);
|
|
||||||
} else {
|
|
||||||
methodHandles[which] = mh;
|
|
||||||
}
|
|
||||||
return mh;
|
return mh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public LambdaForm cachedLambdaForm(int which) {
|
public LambdaForm cachedLambdaForm(int which) {
|
||||||
Object entry = lambdaForms[which];
|
SoftReference<LambdaForm> entry = lambdaForms[which];
|
||||||
if (entry == null) {
|
return (entry != null) ? entry.get() : 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) {
|
public synchronized LambdaForm setCachedLambdaForm(int which, LambdaForm form) {
|
||||||
@ -155,11 +136,7 @@ final class MethodTypeForm {
|
|||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
if (USE_SOFT_CACHE) {
|
lambdaForms[which] = new SoftReference<>(form);
|
||||||
lambdaForms[which] = new SoftReference<>(form);
|
|
||||||
} else {
|
|
||||||
lambdaForms[which] = form;
|
|
||||||
}
|
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +158,7 @@ final class MethodTypeForm {
|
|||||||
* This MTF will stand for that type and all un-erased variations.
|
* This MTF will stand for that type and all un-erased variations.
|
||||||
* Eagerly compute some basic properties of the type, common to all variations.
|
* Eagerly compute some basic properties of the type, common to all variations.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
protected MethodTypeForm(MethodType erasedType) {
|
protected MethodTypeForm(MethodType erasedType) {
|
||||||
this.erasedType = erasedType;
|
this.erasedType = erasedType;
|
||||||
|
|
||||||
@ -221,8 +199,8 @@ final class MethodTypeForm {
|
|||||||
|
|
||||||
this.primitiveCount = primitiveCount;
|
this.primitiveCount = primitiveCount;
|
||||||
this.parameterSlotCount = (short)pslotCount;
|
this.parameterSlotCount = (short)pslotCount;
|
||||||
this.lambdaForms = new Object[LF_LIMIT];
|
this.lambdaForms = new SoftReference[LF_LIMIT];
|
||||||
this.methodHandles = new Object[MH_LIMIT];
|
this.methodHandles = new SoftReference[MH_LIMIT];
|
||||||
} else {
|
} else {
|
||||||
this.basicType = MethodType.methodType(basicReturnType, basicPtypes, true);
|
this.basicType = MethodType.methodType(basicReturnType, basicPtypes, true);
|
||||||
// fill in rest of data from the basic type:
|
// fill in rest of data from the basic type:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -76,10 +76,10 @@ public abstract sealed class Reference<T>
|
|||||||
* indicate end of list.
|
* indicate end of list.
|
||||||
*
|
*
|
||||||
* Dequeued: Added to the associated queue and then removed.
|
* Dequeued: Added to the associated queue and then removed.
|
||||||
* queue = ReferenceQueue.NULL; next = this.
|
* queue = ReferenceQueue.NULL_QUEUE; next = this.
|
||||||
*
|
*
|
||||||
* Unregistered: Not associated with a queue when created.
|
* Unregistered: Not associated with a queue when created.
|
||||||
* queue = ReferenceQueue.NULL.
|
* queue = ReferenceQueue.NULL_QUEUE.
|
||||||
*
|
*
|
||||||
* The collector only needs to examine the referent field and the
|
* The collector only needs to examine the referent field and the
|
||||||
* discovered field to determine whether a (non-FinalReference) Reference
|
* discovered field to determine whether a (non-FinalReference) Reference
|
||||||
@ -161,8 +161,8 @@ public abstract sealed class Reference<T>
|
|||||||
*
|
*
|
||||||
* When registered: the queue with which this reference is registered.
|
* When registered: the queue with which this reference is registered.
|
||||||
* enqueued: ReferenceQueue.ENQUEUE
|
* enqueued: ReferenceQueue.ENQUEUE
|
||||||
* dequeued: ReferenceQueue.NULL
|
* dequeued: ReferenceQueue.NULL_QUEUE
|
||||||
* unregistered: ReferenceQueue.NULL
|
* unregistered: ReferenceQueue.NULL_QUEUE
|
||||||
*/
|
*/
|
||||||
volatile ReferenceQueue<? super T> queue;
|
volatile ReferenceQueue<? super T> queue;
|
||||||
|
|
||||||
@ -232,7 +232,7 @@ public abstract sealed class Reference<T>
|
|||||||
*/
|
*/
|
||||||
private void enqueueFromPending() {
|
private void enqueueFromPending() {
|
||||||
var q = queue;
|
var q = queue;
|
||||||
if (q != ReferenceQueue.NULL) q.enqueue(this);
|
if (q != ReferenceQueue.NULL_QUEUE) q.enqueue(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Object processPendingLock = new Object();
|
private static final Object processPendingLock = new Object();
|
||||||
@ -306,7 +306,12 @@ public abstract sealed class Reference<T>
|
|||||||
handler.start();
|
handler.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called from JVM when loading an AOT cache
|
||||||
static {
|
static {
|
||||||
|
runtimeSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runtimeSetup() {
|
||||||
// provide access in SharedSecrets
|
// provide access in SharedSecrets
|
||||||
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
|
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
|
||||||
@Override
|
@Override
|
||||||
@ -540,7 +545,7 @@ public abstract sealed class Reference<T>
|
|||||||
|
|
||||||
Reference(T referent, ReferenceQueue<? super T> queue) {
|
Reference(T referent, ReferenceQueue<? super T> queue) {
|
||||||
this.referent = referent;
|
this.referent = referent;
|
||||||
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
|
this.queue = (queue == null) ? ReferenceQueue.NULL_QUEUE : queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -55,7 +55,7 @@ public class ReferenceQueue<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final ReferenceQueue<Object> NULL = new Null();
|
static final ReferenceQueue<Object> NULL_QUEUE = new Null();
|
||||||
static final ReferenceQueue<Object> ENQUEUED = new Null();
|
static final ReferenceQueue<Object> ENQUEUED = new Null();
|
||||||
|
|
||||||
private volatile Reference<? extends T> head;
|
private volatile Reference<? extends T> head;
|
||||||
@ -74,7 +74,7 @@ public class ReferenceQueue<T> {
|
|||||||
// Check that since getting the lock this reference hasn't already been
|
// Check that since getting the lock this reference hasn't already been
|
||||||
// enqueued (and even then removed)
|
// enqueued (and even then removed)
|
||||||
ReferenceQueue<?> queue = r.queue;
|
ReferenceQueue<?> queue = r.queue;
|
||||||
if ((queue == NULL) || (queue == ENQUEUED)) {
|
if ((queue == NULL_QUEUE) || (queue == ENQUEUED)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
assert queue == this;
|
assert queue == this;
|
||||||
@ -96,7 +96,7 @@ public class ReferenceQueue<T> {
|
|||||||
private Reference<? extends T> poll0() { // must hold lock
|
private Reference<? extends T> poll0() { // must hold lock
|
||||||
Reference<? extends T> r = head;
|
Reference<? extends T> r = head;
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
r.queue = NULL;
|
r.queue = NULL_QUEUE;
|
||||||
// Update r.queue *before* removing from list, to avoid
|
// Update r.queue *before* removing from list, to avoid
|
||||||
// race with concurrent enqueued checks and fast-path
|
// race with concurrent enqueued checks and fast-path
|
||||||
// poll(). Volatiles ensure ordering.
|
// poll(). Volatiles ensure ordering.
|
||||||
@ -248,7 +248,7 @@ public class ReferenceQueue<T> {
|
|||||||
// still enqueued -> we reached end of chain
|
// still enqueued -> we reached end of chain
|
||||||
r = null;
|
r = null;
|
||||||
} else {
|
} else {
|
||||||
// already dequeued: r.queue == NULL; ->
|
// already dequeued: r.queue == NULL_QUEUE; ->
|
||||||
// restart from head when overtaken by queue poller(s)
|
// restart from head when overtaken by queue poller(s)
|
||||||
r = head;
|
r = head;
|
||||||
}
|
}
|
||||||
|
@ -82,9 +82,36 @@ public class CDS {
|
|||||||
return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0;
|
return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isSingleThreadVM() {
|
||||||
|
return isDumpingStaticArchive();
|
||||||
|
}
|
||||||
|
|
||||||
private static native int getCDSConfigStatus();
|
private static native int getCDSConfigStatus();
|
||||||
private static native void logLambdaFormInvoker(String line);
|
private static native void logLambdaFormInvoker(String line);
|
||||||
|
|
||||||
|
|
||||||
|
// Used only when dumping static archive to keep weak references alive to
|
||||||
|
// ensure that Soft/Weak Reference objects can be reliably archived.
|
||||||
|
private static ArrayList<Object> keepAliveList;
|
||||||
|
|
||||||
|
public static void keepAlive(Object s) {
|
||||||
|
assert isSingleThreadVM(); // no need for synchronization
|
||||||
|
assert isDumpingStaticArchive();
|
||||||
|
if (keepAliveList == null) {
|
||||||
|
keepAliveList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
keepAliveList.add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called by native JVM code at the very end of Java execution before
|
||||||
|
// dumping the static archive.
|
||||||
|
// It collects the objects from keepAliveList so that they can be easily processed
|
||||||
|
// by the native JVM code to check that any Reference objects that need special
|
||||||
|
// clean up must have been registed with keepAlive()
|
||||||
|
private static Object[] getKeepAliveObjects() {
|
||||||
|
return keepAliveList.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize archived static fields in the given Class using archived
|
* Initialize archived static fields in the given Class using archived
|
||||||
* values from CDS dump time. Also initialize the classes of objects in
|
* values from CDS dump time. Also initialize the classes of objects in
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -42,6 +42,7 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.access.SharedSecrets;
|
||||||
|
import jdk.internal.misc.CDS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides management of {@link Map maps} where it is desirable to
|
* This class provides management of {@link Map maps} where it is desirable to
|
||||||
@ -336,6 +337,40 @@ public final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void prepareForAOTCache() {
|
||||||
|
// We are running the AOT assembly phase. The JVM has a single Java thread, so
|
||||||
|
// we don't have any concurrent threads that may modify the map while we are
|
||||||
|
// iterating its keys.
|
||||||
|
//
|
||||||
|
// Also, the java.lang.ref.Reference$ReferenceHandler thread is not running,
|
||||||
|
// so even if the GC has put some of the keys on the pending ReferencePendingList,
|
||||||
|
// none of the keys would have been added to the stale queue yet.
|
||||||
|
assert CDS.isSingleThreadVM();
|
||||||
|
|
||||||
|
for (ReferenceKey<K> key : map.keySet()) {
|
||||||
|
Object referent = key.get();
|
||||||
|
if (referent == null) {
|
||||||
|
// We don't need this key anymore. Add to stale queue
|
||||||
|
((Reference)key).enqueue();
|
||||||
|
} else {
|
||||||
|
// Make sure the referent cannot be collected. Otherwise, when
|
||||||
|
// the referent is collected, the GC may push the key onto
|
||||||
|
// Universe::reference_pending_list() at an unpredictable time,
|
||||||
|
// making it difficult to correctly serialize the key's
|
||||||
|
// state into the CDS archive.
|
||||||
|
//
|
||||||
|
// See aotReferenceObjSupport.cpp for more info.
|
||||||
|
CDS.keepAlive(referent);
|
||||||
|
}
|
||||||
|
Reference.reachabilityFence(referent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all keys enqueued above
|
||||||
|
removeStaleReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts an entry where the key and the value are the same. Used for
|
* Puts an entry where the key and the value are the same. Used for
|
||||||
* interning values in a set.
|
* interning values in a set.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -193,4 +193,8 @@ public final class ReferencedKeySet<T> extends AbstractSet<T> {
|
|||||||
public T intern(T e, UnaryOperator<T> interner) {
|
public T intern(T e, UnaryOperator<T> interner) {
|
||||||
return ReferencedKeyMap.intern(map, e, interner);
|
return ReferencedKeyMap.intern(map, e, interner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void prepareForAOTCache() {
|
||||||
|
map.prepareForAOTCache();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @summary This is a test case for creating an AOT cache using the setup_aot/TestSetupAOT.java program, which
|
||||||
|
* is used for running HotSpot tests in the "AOT mode"
|
||||||
|
* (E.g., make test JTREG=AOT_JDK=true TEST=open/test/hotspot/jtreg/runtime/invokedynamic)
|
||||||
|
* @requires vm.cds
|
||||||
|
* @comment work around JDK-8345635
|
||||||
|
* @requires !vm.jvmci.enabled
|
||||||
|
* @library /test/lib /test/setup_aot
|
||||||
|
* @build TestSetupAOTTest JavacBenchApp TestSetupAOT
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller
|
||||||
|
* TestSetupAOT
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar
|
||||||
|
* TestSetupAOT
|
||||||
|
* TestSetupAOT$ToolOutput
|
||||||
|
* JavacBenchApp
|
||||||
|
* JavacBenchApp$ClassFile
|
||||||
|
* JavacBenchApp$FileManager
|
||||||
|
* JavacBenchApp$SourceFile
|
||||||
|
* @run driver TestSetupAOTTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jdk.test.lib.cds.SimpleCDSAppTester;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
|
||||||
|
public class TestSetupAOTTest {
|
||||||
|
public static void main(String... args) throws Exception {
|
||||||
|
SimpleCDSAppTester.of("TestSetupAOT")
|
||||||
|
.classpath("app.jar")
|
||||||
|
.appCommandLine("TestSetupAOT", ".")
|
||||||
|
.runAOTWorkflow();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test various test cases for archived WeakReference objects.
|
||||||
|
* @requires vm.cds.write.archived.java.heap
|
||||||
|
* @requires vm.cds.supports.aot.class.linking
|
||||||
|
* @requires vm.debug
|
||||||
|
* @comment work around JDK-8345635
|
||||||
|
* @requires !vm.jvmci.enabled
|
||||||
|
* @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds
|
||||||
|
* @build WeakReferenceTest
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar weakref.jar
|
||||||
|
* WeakReferenceTestApp WeakReferenceTestApp$Inner ShouldNotBeAOTInited ShouldNotBeArchived SharedQueue
|
||||||
|
* WeakReferenceTestBadApp1 WeakReferenceTestBadApp2
|
||||||
|
* @run driver WeakReferenceTest AOT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import jdk.test.lib.cds.CDSAppTester;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
import jdk.test.lib.helpers.ClassFileInstaller;
|
||||||
|
import jtreg.SkippedException;
|
||||||
|
|
||||||
|
public class WeakReferenceTest {
|
||||||
|
static final String appJar = ClassFileInstaller.getJarPath("weakref.jar");
|
||||||
|
|
||||||
|
static final String goodApp = "WeakReferenceTestApp";
|
||||||
|
static final String badApp1 = "WeakReferenceTestBadApp1";
|
||||||
|
static final String badApp2 = "WeakReferenceTestBadApp2";
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
new Tester(goodApp).run(args);
|
||||||
|
|
||||||
|
runBadApp(badApp1, args);
|
||||||
|
runBadApp(badApp2, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runBadApp(String badApp, String[] args) throws Exception {
|
||||||
|
try {
|
||||||
|
new Tester(badApp).run(args);
|
||||||
|
throw new RuntimeException(badApp + " did not fail in assembly phase as expected");
|
||||||
|
} catch (SkippedException e) {
|
||||||
|
System.out.println("Negative test: expected SkippedException");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Tester extends CDSAppTester {
|
||||||
|
String mainClass;
|
||||||
|
public Tester(String mainClass) {
|
||||||
|
super(mainClass);
|
||||||
|
this.mainClass = mainClass;
|
||||||
|
|
||||||
|
if (mainClass != goodApp) {
|
||||||
|
setCheckExitValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String classpath(RunMode runMode) {
|
||||||
|
return appJar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] vmArgs(RunMode runMode) {
|
||||||
|
if (runMode == RunMode.ASSEMBLY) {
|
||||||
|
return new String[] {
|
||||||
|
"-Xlog:gc,cds+class=debug",
|
||||||
|
"-XX:AOTInitTestClass=" + mainClass,
|
||||||
|
"-Xlog:cds+map,cds+map+oops=trace:file=cds.oops.txt:none:filesize=0",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return new String[] {
|
||||||
|
"-Xlog:gc",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] appCommandLine(RunMode runMode) {
|
||||||
|
return new String[] {
|
||||||
|
mainClass,
|
||||||
|
runMode.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
|
||||||
|
if (runMode == RunMode.ASSEMBLY && mainClass != goodApp) {
|
||||||
|
out.shouldNotHaveExitValue(0);
|
||||||
|
out.shouldMatch("Cannot archive reference object .* of class java.lang.ref.WeakReference");
|
||||||
|
if (mainClass == badApp1) {
|
||||||
|
out.shouldContain("referent cannot be null");
|
||||||
|
} else {
|
||||||
|
out.shouldContain("referent is not registered with CDS.keepAlive()");
|
||||||
|
}
|
||||||
|
throw new SkippedException("Assembly phase expected to fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
out.shouldHaveExitValue(0);
|
||||||
|
out.shouldNotContain("Unexpected exception:");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WeakReferenceTestApp {
|
||||||
|
static class Inner { // This class is NOT aot-initialized
|
||||||
|
static boolean WeakReferenceTestApp_clinit_executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
Inner.WeakReferenceTestApp_clinit_executed = true;
|
||||||
|
|
||||||
|
// This static {} block is executed the training run (which uses no AOT cache).
|
||||||
|
//
|
||||||
|
// During the assembly phase, this static {} block of is also executed
|
||||||
|
// (triggered by the -XX:AOTInitTestClass=WeakReferenceTestApp flag).
|
||||||
|
// It runs the aot_init_for_testXXX() method to set up the aot-initialized data structures
|
||||||
|
// that are used by each testXXX() function.
|
||||||
|
//
|
||||||
|
// This block is NOT executed during the production run, because WeakReferenceTestApp
|
||||||
|
// is aot-initialized.
|
||||||
|
|
||||||
|
aot_init_for_testCollectedInAssembly();
|
||||||
|
aot_init_for_testWeakReferenceCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
runTests(args);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.err.println("Unexpected exception:");
|
||||||
|
t.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runTests(String[] args) throws Exception {
|
||||||
|
boolean isProduction = args[0].equals("PRODUCTION");
|
||||||
|
|
||||||
|
if (isProduction && Inner.WeakReferenceTestApp_clinit_executed) {
|
||||||
|
throw new RuntimeException("WeakReferenceTestApp should have been aot-inited");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isProduction) {
|
||||||
|
// A GC should have happened before the heap objects are written into
|
||||||
|
// the AOT cache. So any unreachable referents should have been collected.
|
||||||
|
} else {
|
||||||
|
// We are in the training run. Simulate the GC mentioned in the above comment,
|
||||||
|
// so the test cases should observe the same states as in the production run.
|
||||||
|
System.gc();
|
||||||
|
}
|
||||||
|
|
||||||
|
testCollectedInAssembly(isProduction);
|
||||||
|
testWeakReferenceCollection(isProduction);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Set up for testCollectedInAssembly()
|
||||||
|
static WeakReference refToCollectedObj;
|
||||||
|
|
||||||
|
static void aot_init_for_testCollectedInAssembly() {
|
||||||
|
// The referent will be GC-ed in the assembly run when the JVM forces a full GC.
|
||||||
|
refToCollectedObj = new WeakReference(new String("collected in assembly"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// [TEST CASE] Test the storage of a WeakReference whose referent has been collected during the assembly phase.
|
||||||
|
static void testCollectedInAssembly(boolean isProduction) {
|
||||||
|
System.out.println("refToCollectedObj.get() = " + refToCollectedObj.get());
|
||||||
|
|
||||||
|
if (refToCollectedObj.get() != null) {
|
||||||
|
throw new RuntimeException("refToCollectedObj.get() should have been GC'ed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Set up for testWeakReferenceCollection()
|
||||||
|
static Object root;
|
||||||
|
static WeakReference ref;
|
||||||
|
|
||||||
|
static void aot_init_for_testWeakReferenceCollection() {
|
||||||
|
root = new String("to be collected in production");
|
||||||
|
ref = makeRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
static WeakReference makeRef() {
|
||||||
|
System.out.println("WeakReferenceTestApp::makeRef() is executed");
|
||||||
|
WeakReference r = new WeakReference(root);
|
||||||
|
System.out.println("r.get() = " + r.get());
|
||||||
|
|
||||||
|
ShouldNotBeAOTInited.doit();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static WeakReference makeRef2() {
|
||||||
|
return new WeakReference(new String("to be collected in production"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// [TEST CASE] A WeakReference allocated in assembly phase should be collectable in the production run
|
||||||
|
static void testWeakReferenceCollection(boolean isProduction) {
|
||||||
|
WeakReference ref2 = makeRef2();
|
||||||
|
System.out.println("ref.get() = " + ref.get()); // created during assembly phase
|
||||||
|
System.out.println("ref2.get() = " + ref2.get()); // created during production run
|
||||||
|
|
||||||
|
if (ref.get() == null) {
|
||||||
|
throw new RuntimeException("ref.get() should not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("... running GC ...");
|
||||||
|
root = null; // make ref.referent() eligible for collection
|
||||||
|
System.gc();
|
||||||
|
|
||||||
|
System.out.println("ref.get() = " + ref.get());
|
||||||
|
System.out.println("ref2.get() = " + ref2.get());
|
||||||
|
|
||||||
|
if (ref.get() != null) {
|
||||||
|
throw new RuntimeException("ref.get() should be null");
|
||||||
|
}
|
||||||
|
if (ref2.get() != null) {
|
||||||
|
throw new RuntimeException("ref2.get() should be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("ShouldNotBeAOTInited.doit_executed = " + ShouldNotBeAOTInited.doit_executed);
|
||||||
|
if (isProduction && ShouldNotBeAOTInited.doit_executed) {
|
||||||
|
throw new RuntimeException("ShouldNotBeAOTInited should not have been aot-inited");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShouldNotBeAOTInited {
|
||||||
|
static WeakReference ref;
|
||||||
|
static boolean doit_executed;
|
||||||
|
static {
|
||||||
|
System.out.println("ShouldNotBeAOTInited.<clinit> called");
|
||||||
|
}
|
||||||
|
static void doit() {
|
||||||
|
System.out.println("ShouldNotBeAOTInited.doit()> called");
|
||||||
|
doit_executed = true;
|
||||||
|
ref = new WeakReference(new ShouldNotBeAOTInited());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShouldNotBeArchived {
|
||||||
|
static ShouldNotBeArchived instance = new ShouldNotBeArchived();
|
||||||
|
static WeakReference ref;
|
||||||
|
static int state = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SharedQueue {
|
||||||
|
static SharedQueue sharedQueueInstance = new SharedQueue();
|
||||||
|
private ReferenceQueue<Object> theQueue = new ReferenceQueue<Object>();
|
||||||
|
|
||||||
|
static ReferenceQueue<Object> queue() {
|
||||||
|
return sharedQueueInstance.theQueue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WeakReferenceTestBadApp1 {
|
||||||
|
static WeakReference refWithQueue;
|
||||||
|
static SharedQueue sharedQueueInstance;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// See comments in aotReferenceObjSupport.cpp: group [2] references cannot have null referent.
|
||||||
|
sharedQueueInstance = SharedQueue.sharedQueueInstance;
|
||||||
|
refWithQueue = new WeakReference(String.class, SharedQueue.queue());
|
||||||
|
refWithQueue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String args[]) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WeakReferenceTestBadApp2 {
|
||||||
|
static WeakReference refWithQueue;
|
||||||
|
static SharedQueue sharedQueueInstance;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// See comments in aotReferenceObjSupport.cpp: group [2] references must be registered with CDS.keepAlive()
|
||||||
|
sharedQueueInstance = SharedQueue.sharedQueueInstance;
|
||||||
|
refWithQueue = new WeakReference(String.class, SharedQueue.queue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String args[]) {}
|
||||||
|
}
|
@ -23,12 +23,17 @@
|
|||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.spi.ToolProvider;
|
import java.util.spi.ToolProvider;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import static java.util.stream.Collectors.*;
|
import static java.util.stream.Collectors.*;
|
||||||
@ -60,6 +65,42 @@ public class TestSetupAOT {
|
|||||||
LOGGER.log(Level.FINE, "Done");
|
LOGGER.log(Level.FINE, "Done");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class ToolOutput {
|
||||||
|
ByteArrayOutputStream baos;
|
||||||
|
PrintStream ps;
|
||||||
|
String output;
|
||||||
|
|
||||||
|
ToolOutput() throws Exception {
|
||||||
|
baos = new ByteArrayOutputStream();
|
||||||
|
ps = new PrintStream(baos, true, StandardCharsets.UTF_8.name());
|
||||||
|
}
|
||||||
|
void finish() throws Exception {
|
||||||
|
output = baos.toString(StandardCharsets.UTF_8.name());
|
||||||
|
System.out.println(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolOutput shouldContain(String... substrings) {
|
||||||
|
for (String s : substrings) {
|
||||||
|
if (!output.contains(s)) {
|
||||||
|
throw new RuntimeException("\"" + s + "\" missing from tool output");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolOutput shouldMatch(String... regexps) {
|
||||||
|
for (String regexp : regexps) {
|
||||||
|
Pattern pattern = Pattern.compile(regexp, Pattern.MULTILINE);
|
||||||
|
if (!pattern.matcher(output).find()) {
|
||||||
|
throw new RuntimeException("Pattern \"" + regexp + "\" missing from tool output");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void runJDKTools(String[] args) throws Throwable {
|
static void runJDKTools(String[] args) throws Throwable {
|
||||||
String tmpDir = args[0];
|
String tmpDir = args[0];
|
||||||
System.out.println("Working Directory = " + System.getProperty("user.dir"));
|
System.out.println("Working Directory = " + System.getProperty("user.dir"));
|
||||||
@ -68,26 +109,33 @@ public class TestSetupAOT {
|
|||||||
// ------------------------------
|
// ------------------------------
|
||||||
// javac
|
// javac
|
||||||
|
|
||||||
execTool("javac", "--help");
|
execTool("javac", "--help")
|
||||||
|
.shouldContain("Usage: javac <options> <source files>");
|
||||||
|
|
||||||
JavacBenchApp.main(new String[] {"5"});
|
JavacBenchApp.main(new String[] {"5"});
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
// javap
|
// javap
|
||||||
|
|
||||||
execTool("javap", "--help");
|
execTool("javap", "--help")
|
||||||
|
.shouldContain("Show package/protected/public classes");
|
||||||
execTool("javap", "-c", "-private", "-v", "-verify",
|
execTool("javap", "-c", "-private", "-v", "-verify",
|
||||||
"java.lang.System",
|
"java.lang.System",
|
||||||
"java/util/stream/IntStream",
|
"java/util/stream/IntStream",
|
||||||
"jdk.internal.module.ModuleBootstrap");
|
"jdk.internal.module.ModuleBootstrap")
|
||||||
|
.shouldContain("Compiled from \"System.java\"",
|
||||||
|
"public static java.io.Console console()");
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
// jlink
|
// jlink
|
||||||
|
|
||||||
String jlinkOutput = tmpDir + File.separator + "jlinkOutput";
|
String jlinkOutput = tmpDir + File.separator + "jlinkOutput";
|
||||||
|
|
||||||
execTool("jlink", "--help");
|
execTool("jlink", "--help")
|
||||||
execTool("jlink", "--list-plugins");
|
.shouldContain("Compression to use in compressing resources");
|
||||||
|
execTool("jlink", "--list-plugins")
|
||||||
|
.shouldContain("List of available plugins",
|
||||||
|
"--generate-cds-archive ");
|
||||||
|
|
||||||
deleteAll(jlinkOutput);
|
deleteAll(jlinkOutput);
|
||||||
execTool("jlink", "--add-modules", "java.base", "--strip-debug", "--output", jlinkOutput);
|
execTool("jlink", "--add-modules", "java.base", "--strip-debug", "--output", jlinkOutput);
|
||||||
@ -98,20 +146,27 @@ public class TestSetupAOT {
|
|||||||
|
|
||||||
String jarOutput = tmpDir + File.separator + "tmp.jar";
|
String jarOutput = tmpDir + File.separator + "tmp.jar";
|
||||||
|
|
||||||
execTool("jar", "--help");
|
execTool("jar", "--help")
|
||||||
|
.shouldContain("--main-class=CLASSNAME");
|
||||||
|
|
||||||
deleteAll(jarOutput);
|
deleteAll(jarOutput);
|
||||||
execTool("jar", "cvf", jarOutput, "TestSetupAOT.class");
|
execTool("jar", "cvf", jarOutput, "TestSetupAOT.class")
|
||||||
execTool("jar", "uvf", jarOutput, "TestSetupAOT.class");
|
.shouldContain("adding: TestSetupAOT.class");
|
||||||
execTool("jar", "tvf", jarOutput);
|
execTool("jar", "uvf", jarOutput, "TestSetupAOT.class")
|
||||||
execTool("jar", "--describe-module", "--file=" + jarOutput);
|
.shouldContain("adding: TestSetupAOT.class");
|
||||||
|
execTool("jar", "tvf", jarOutput)
|
||||||
|
.shouldContain("META-INF/MANIFEST.MF");
|
||||||
|
execTool("jar", "--describe-module", "--file=" + jarOutput)
|
||||||
|
.shouldMatch("Unable to derive module descriptor for: .*tmp.jar");
|
||||||
deleteAll(jarOutput);
|
deleteAll(jarOutput);
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
// jdeps
|
// jdeps
|
||||||
|
|
||||||
execTool("jdeps", "--help");
|
execTool("jdeps", "--help")
|
||||||
execTool("jdeps", "-v", "TestSetupAOT.class");
|
.shouldContain("--ignore-missing-deps");
|
||||||
|
execTool("jdeps", "-v", "TestSetupAOT.class")
|
||||||
|
.shouldContain("-> JavacBenchApp");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void deleteAll(String f) {
|
static void deleteAll(String f) {
|
||||||
@ -129,7 +184,7 @@ public class TestSetupAOT {
|
|||||||
f.delete();
|
f.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void execTool(String tool, String... args) throws Throwable {
|
static ToolOutput execTool(String tool, String... args) throws Throwable {
|
||||||
System.out.println("== Running tool ======================================================");
|
System.out.println("== Running tool ======================================================");
|
||||||
System.out.print(tool);
|
System.out.print(tool);
|
||||||
for (String s : args) {
|
for (String s : args) {
|
||||||
@ -138,9 +193,13 @@ public class TestSetupAOT {
|
|||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println("======================================================================");
|
System.out.println("======================================================================");
|
||||||
|
|
||||||
|
ToolOutput output = new ToolOutput();
|
||||||
ToolProvider t = ToolProvider.findFirst(tool)
|
ToolProvider t = ToolProvider.findFirst(tool)
|
||||||
.orElseThrow(() -> new RuntimeException(tool + " not found"));
|
.orElseThrow(() -> new RuntimeException(tool + " not found"));
|
||||||
t.run(System.out, System.out, args);
|
t.run(output.ps, output.ps, args);
|
||||||
|
|
||||||
|
output.finish();
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user