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:
Mat Carter 2025-04-30 18:05:59 +00:00 committed by Ioi Lam
parent 9a2a2c5bb1
commit 1ff7e813e3
23 changed files with 874 additions and 177 deletions

View File

@ -25,6 +25,7 @@
#include "cds/aotClassLinker.hpp"
#include "cds/aotArtifactFinder.hpp"
#include "cds/aotClassInitializer.hpp"
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/dumpTimeClassInfo.inline.hpp"
#include "cds/heapShared.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
// into the AOT cache -- that will be decided by the code below.
SystemDictionaryShared::finish_exclusion_checks();
AOTReferenceObjSupport::init_keep_alive_objs_table();
start_scanning_for_oops();

View File

@ -338,7 +338,8 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
bool AOTClassInitializer::is_runtime_setup_required(InstanceKlass* ik) {
return ik == vmClasses::Class_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) {

View 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

View 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

View File

@ -22,6 +22,7 @@
*
*/
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/archiveHeapWriter.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/filemap.hpp"
@ -607,18 +608,27 @@ class ArchiveHeapWriter::EmbeddedOopRelocator: public BasicOopIterateClosure {
oop _src_obj;
address _buffered_obj;
CHeapBitMap* _oopmap;
bool _is_java_lang_ref;
public:
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( oop *p) { EmbeddedOopRelocator::do_oop_work(p); }
private:
template <class T> void do_oop_work(T *p) {
size_t field_offset = pointer_delta(p, _src_obj, sizeof(char));
ArchiveHeapWriter::relocate_field_in_buffer<T>((T*)(_buffered_obj + field_offset), _oopmap);
int field_offset = pointer_delta_as_int((char*)p, cast_from_oop<char*>(_src_obj));
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);
}
}
};

View File

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

View File

@ -25,6 +25,7 @@
#include "cds/aotArtifactFinder.hpp"
#include "cds/aotClassInitializer.hpp"
#include "cds/aotClassLocation.hpp"
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/archiveHeapLoader.hpp"
#include "cds/archiveHeapWriter.hpp"
@ -1363,16 +1364,18 @@ void HeapShared::clear_archived_roots_of(Klass* k) {
}
}
// Push all oops that are referenced by _referencing_obj onto the _stack.
class HeapShared::ReferentPusher: public BasicOopIterateClosure {
// Push all oop fields (or oop array elemenets in case of an objArray) in
// _referencing_obj onto the _stack.
class HeapShared::OopFieldPusher: public BasicOopIterateClosure {
PendingOopStack* _stack;
GrowableArray<oop> _found_oop_fields;
int _level;
bool _record_klasses_only;
KlassSubGraphInfo* _subgraph_info;
oop _referencing_obj;
bool _is_java_lang_ref;
public:
ReferentPusher(PendingOopStack* stack,
OopFieldPusher(PendingOopStack* stack,
int level,
bool record_klasses_only,
KlassSubGraphInfo* subgraph_info,
@ -1383,14 +1386,15 @@ class HeapShared::ReferentPusher: public BasicOopIterateClosure {
_record_klasses_only(record_klasses_only),
_subgraph_info(subgraph_info),
_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( oop *p) { ReferentPusher::do_oop_work(p); }
void do_oop(narrowOop *p) { OopFieldPusher::do_oop_work(p); }
void do_oop( oop *p) { OopFieldPusher::do_oop_work(p); }
~ReferentPusher() {
~OopFieldPusher() {
while (_found_oop_fields.length() > 0) {
// 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.
oop obj = _found_oop_fields.pop();
_stack->push(PendingOop(obj, _referencing_obj, _level + 1));
@ -1399,14 +1403,18 @@ class HeapShared::ReferentPusher: public BasicOopIterateClosure {
protected:
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)) {
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)) {
ResourceMark rm;
log_debug(cds, heap)("(%d) %s[%zu] ==> " PTR_FORMAT " size %zu %s", _level,
_referencing_obj->klass()->external_name(), field_delta,
log_debug(cds, heap)("(%d) %s[%d] ==> " PTR_FORMAT " size %zu %s", _level,
_referencing_obj->klass()->external_name(), field_offset,
p2i(obj), obj->size() * HeapWordSize, obj->klass()->external_name());
if (log_is_enabled(Trace, cds, heap)) {
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
// so we can work on them next.
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);
}
@ -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
// 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
// within a Class instance (java mirror). If the static field is a

View File

@ -164,8 +164,8 @@ private:
static void count_allocation(size_t size);
static void print_stats();
static void debug_trace();
public:
static void debug_trace();
static unsigned oop_hash(oop const& p);
static unsigned string_oop_hash(oop const& string) {
return java_lang_String::hash_code(string);
@ -357,7 +357,7 @@ private:
int level() const { return _level; }
};
class ReferentPusher;
class OopFieldPusher;
using PendingOopStack = GrowableArrayCHeap<PendingOop, mtClassShared>;
static PendingOop _object_being_archived;

View File

@ -28,6 +28,7 @@
#include "cds/aotClassLocation.hpp"
#include "cds/aotConstantPoolResolver.hpp"
#include "cds/aotLinkedClassBulkLoader.hpp"
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/archiveHeapLoader.hpp"
#include "cds/archiveHeapWriter.hpp"
@ -962,22 +963,14 @@ void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS
#if INCLUDE_CDS_JAVA_HEAP
if (CDSConfig::is_dumping_heap()) {
ArchiveHeapWriter::init();
if (CDSConfig::is_dumping_full_module_graph()) {
ClassLoaderDataShared::ensure_module_entry_tables_exist();
HeapShared::reset_archived_object_states(CHECK);
}
if (CDSConfig::is_dumping_method_handles()) {
// 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);
}
AOTReferenceObjSupport::initialize(CHECK);
AOTReferenceObjSupport::stabilize_cached_reference_objects(CHECK);
if (CDSConfig::is_initing_classes_at_dump_time()) {
// java.lang.Class::reflectionFactory cannot be archived yet. We set this field

View File

@ -22,6 +22,7 @@
*
*/
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/archiveHeapLoader.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())) {
// It's problematic to archive Reference objects. One of the reasons is that
// Reference::discovered may pull in unwanted objects (see JDK-8284336)
if (!AOTReferenceObjSupport::is_enabled() && klass->is_subclass_of(vmClasses::Reference_klass())) {
return false;
}

View File

@ -719,7 +719,6 @@ class SerializeClosure;
JFR_TEMPLATES(template) \
\
/* CDS */ \
template(createArchivedObjects, "createArchivedObjects") \
template(dumpSharedArchive, "dumpSharedArchive") \
template(dumpSharedArchive_signature, "(ZLjava/lang/String;)Ljava/lang/String;") \
template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \

View File

@ -38,7 +38,6 @@ 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.
@ -90,17 +89,12 @@ class LambdaFormEditor {
* Tightly coupled with the TransformKey class, which is used to lookup existing
* Transforms.
*/
private static final class Transform {
final Object cache;
private static final class Transform extends SoftReference<LambdaForm> {
final long packedBytes;
final byte[] fullBytes;
private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) {
if (USE_SOFT_CACHE) {
cache = new SoftReference<LambdaForm>(result);
} else {
cache = result;
}
super(result);
this.packedBytes = packedBytes;
this.fullBytes = fullBytes;
}
@ -141,15 +135,6 @@ class LambdaFormEditor {
}
return buf.toString();
}
@SuppressWarnings("unchecked")
public LambdaForm get() {
if (cache instanceof LambdaForm lf) {
return lf;
} else {
return ((SoftReference<LambdaForm>)cache).get();
}
}
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 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.
*
* 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
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"));
}
}

View File

@ -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.
*
* 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.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;
@ -42,7 +40,6 @@ 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;
@ -394,17 +391,6 @@ 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;
@ -425,7 +411,6 @@ class MethodType
}
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.
@ -1397,29 +1382,9 @@ s.writeObject(this.parameterArray());
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();
private static void assemblySetup() {
internTable.prepareForAOTCache();
}
}

View File

@ -30,7 +30,6 @@ 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
@ -52,7 +51,7 @@ final class MethodTypeForm {
final MethodType basicType; // the canonical erasure, with primitives simplified
// Cached adapter information:
private final Object[] methodHandles;
private final SoftReference<MethodHandle>[] methodHandles;
// Indexes into methodHandles:
static final int
@ -62,7 +61,7 @@ final class MethodTypeForm {
MH_LIMIT = 3;
// Cached lambda form information, for basic types only:
private final Object[] lambdaForms;
private final SoftReference<LambdaForm>[] lambdaForms;
private SoftReference<MemberName> interpretEntry;
@ -111,16 +110,9 @@ final class MethodTypeForm {
return basicType;
}
@SuppressWarnings("unchecked")
public MethodHandle cachedMethodHandle(int which) {
Object entry = methodHandles[which];
if (entry == null) {
return null;
} else if (entry instanceof MethodHandle mh) {
return mh;
} else {
return ((SoftReference<MethodHandle>)entry).get();
}
SoftReference<MethodHandle> entry = methodHandles[which];
return (entry != null) ? entry.get() : null;
}
public synchronized MethodHandle setCachedMethodHandle(int which, MethodHandle mh) {
@ -129,24 +121,13 @@ final class MethodTypeForm {
if (prev != null) {
return prev;
}
if (USE_SOFT_CACHE) {
methodHandles[which] = new SoftReference<>(mh);
} else {
methodHandles[which] = mh;
}
return mh;
}
@SuppressWarnings("unchecked")
public LambdaForm cachedLambdaForm(int which) {
Object entry = lambdaForms[which];
if (entry == null) {
return null;
} else if (entry instanceof LambdaForm lf) {
return lf;
} else {
return ((SoftReference<LambdaForm>)entry).get();
}
SoftReference<LambdaForm> entry = lambdaForms[which];
return (entry != null) ? entry.get() : null;
}
public synchronized LambdaForm setCachedLambdaForm(int which, LambdaForm form) {
@ -155,11 +136,7 @@ final class MethodTypeForm {
if (prev != null) {
return prev;
}
if (USE_SOFT_CACHE) {
lambdaForms[which] = new SoftReference<>(form);
} else {
lambdaForms[which] = form;
}
return form;
}
@ -181,6 +158,7 @@ final class MethodTypeForm {
* This MTF will stand for that type and all un-erased variations.
* Eagerly compute some basic properties of the type, common to all variations.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected MethodTypeForm(MethodType erasedType) {
this.erasedType = erasedType;
@ -221,8 +199,8 @@ final class MethodTypeForm {
this.primitiveCount = primitiveCount;
this.parameterSlotCount = (short)pslotCount;
this.lambdaForms = new Object[LF_LIMIT];
this.methodHandles = new Object[MH_LIMIT];
this.lambdaForms = new SoftReference[LF_LIMIT];
this.methodHandles = new SoftReference[MH_LIMIT];
} else {
this.basicType = MethodType.methodType(basicReturnType, basicPtypes, true);
// fill in rest of data from the basic type:

View File

@ -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.
*
* 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.
*
* 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.
* queue = ReferenceQueue.NULL.
* queue = ReferenceQueue.NULL_QUEUE.
*
* The collector only needs to examine the referent field and the
* 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.
* enqueued: ReferenceQueue.ENQUEUE
* dequeued: ReferenceQueue.NULL
* unregistered: ReferenceQueue.NULL
* dequeued: ReferenceQueue.NULL_QUEUE
* unregistered: ReferenceQueue.NULL_QUEUE
*/
volatile ReferenceQueue<? super T> queue;
@ -232,7 +232,7 @@ public abstract sealed class Reference<T>
*/
private void enqueueFromPending() {
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();
@ -306,7 +306,12 @@ public abstract sealed class Reference<T>
handler.start();
}
// Called from JVM when loading an AOT cache
static {
runtimeSetup();
}
private static void runtimeSetup() {
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
@ -540,7 +545,7 @@ public abstract sealed class Reference<T>
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
this.queue = (queue == null) ? ReferenceQueue.NULL_QUEUE : queue;
}
/**

View File

@ -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.
*
* 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();
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
// enqueued (and even then removed)
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
if ((queue == NULL_QUEUE) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
@ -96,7 +96,7 @@ public class ReferenceQueue<T> {
private Reference<? extends T> poll0() { // must hold lock
Reference<? extends T> r = head;
if (r != null) {
r.queue = NULL;
r.queue = NULL_QUEUE;
// Update r.queue *before* removing from list, to avoid
// race with concurrent enqueued checks and fast-path
// poll(). Volatiles ensure ordering.
@ -248,7 +248,7 @@ public class ReferenceQueue<T> {
// still enqueued -> we reached end of chain
r = null;
} else {
// already dequeued: r.queue == NULL; ->
// already dequeued: r.queue == NULL_QUEUE; ->
// restart from head when overtaken by queue poller(s)
r = head;
}

View File

@ -82,9 +82,36 @@ public class CDS {
return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0;
}
public static boolean isSingleThreadVM() {
return isDumpingStaticArchive();
}
private static native int getCDSConfigStatus();
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
* values from CDS dump time. Also initialize the classes of objects in

View File

@ -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.
*
* 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 jdk.internal.access.SharedSecrets;
import jdk.internal.misc.CDS;
/**
* 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
* interning values in a set.

View File

@ -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.
*
* 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) {
return ReferencedKeyMap.intern(map, e, interner);
}
public void prepareForAOTCache() {
map.prepareForAOTCache();
}
}

View File

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

View File

@ -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[]) {}
}

View File

@ -23,12 +23,17 @@
* questions.
*/
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.spi.ToolProvider;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;
@ -60,6 +65,42 @@ public class TestSetupAOT {
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 {
String tmpDir = args[0];
System.out.println("Working Directory = " + System.getProperty("user.dir"));
@ -68,26 +109,33 @@ public class TestSetupAOT {
// ------------------------------
// javac
execTool("javac", "--help");
execTool("javac", "--help")
.shouldContain("Usage: javac <options> <source files>");
JavacBenchApp.main(new String[] {"5"});
// ------------------------------
// javap
execTool("javap", "--help");
execTool("javap", "--help")
.shouldContain("Show package/protected/public classes");
execTool("javap", "-c", "-private", "-v", "-verify",
"java.lang.System",
"java/util/stream/IntStream",
"jdk.internal.module.ModuleBootstrap");
"jdk.internal.module.ModuleBootstrap")
.shouldContain("Compiled from \"System.java\"",
"public static java.io.Console console()");
// ------------------------------
// jlink
String jlinkOutput = tmpDir + File.separator + "jlinkOutput";
execTool("jlink", "--help");
execTool("jlink", "--list-plugins");
execTool("jlink", "--help")
.shouldContain("Compression to use in compressing resources");
execTool("jlink", "--list-plugins")
.shouldContain("List of available plugins",
"--generate-cds-archive ");
deleteAll(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";
execTool("jar", "--help");
execTool("jar", "--help")
.shouldContain("--main-class=CLASSNAME");
deleteAll(jarOutput);
execTool("jar", "cvf", jarOutput, "TestSetupAOT.class");
execTool("jar", "uvf", jarOutput, "TestSetupAOT.class");
execTool("jar", "tvf", jarOutput);
execTool("jar", "--describe-module", "--file=" + jarOutput);
execTool("jar", "cvf", jarOutput, "TestSetupAOT.class")
.shouldContain("adding: TestSetupAOT.class");
execTool("jar", "uvf", jarOutput, "TestSetupAOT.class")
.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);
// ------------------------------
// jdeps
execTool("jdeps", "--help");
execTool("jdeps", "-v", "TestSetupAOT.class");
execTool("jdeps", "--help")
.shouldContain("--ignore-missing-deps");
execTool("jdeps", "-v", "TestSetupAOT.class")
.shouldContain("-> JavacBenchApp");
}
static void deleteAll(String f) {
@ -129,7 +184,7 @@ public class TestSetupAOT {
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.print(tool);
for (String s : args) {
@ -138,9 +193,13 @@ public class TestSetupAOT {
System.out.println();
System.out.println("======================================================================");
ToolOutput output = new ToolOutput();
ToolProvider t = ToolProvider.findFirst(tool)
.orElseThrow(() -> new RuntimeException(tool + " not found"));
t.run(System.out, System.out, args);
t.run(output.ps, output.ps, args);
output.finish();
return output;
}