Skip to content

8354897: Support Soft/Weak Reference in AOT cache #24757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

iklam
Copy link
Member

@iklam iklam commented Apr 18, 2025

This PR contains 2 parts

  • Upstream of Soft/Weak Reference support authored by @macarte from the Leyden repo
  • New C++ class AOTReferenceObjSupport and new Java method ReferencedKeyMap::prepareForAOTCache() developed by @iklam on the advice of @fisk from the GC team. These control the lifecycles of reference objects during the assembly phase to simplify the implementation.

One problem we faced in this PR is the handling of Reference objects that are waiting for clean up. Currently, the only cached Reference objects that require clean up are the WeakReferenceKeys used by ReferencedKeyMap (which is used by MethodType::internTable):

  • When the referent of a WeakReferenceKey K has been collected, the key will be placed on Universe::reference_pending_list(). It's linked to other pending references with the Reference::discovered field. At this point, K is still stored in the ReferencedKeyMap.
  • When heapShared.cpp discovered the ReferencedKeyMap, it will discover K, and it may also discover other pending references that are not intended for the AOT cache. As a result, we end up caching unnecessary objects.

ReferencedKeyMap::prepareForAOTCache() avoids the above problem. It goes over all entries in the table:

  • If an entry has not yet been collected, we make sure it will never be collected.
  • If an entry has been collected, we remove it from the table

Therefore, by the time heapShared.cpp starts scanning the ReferencedKeyMap, it will never see any keys that are on the pending list, so we will not see unintended objects.

This implementation is the very first step of Reference support in the AOT cache, so we chose a simplified approach that makes no assumptions on when the pending reference list is processed. This is sufficient for the current set of references objects in the AOT cache.

In the future, we may relax the implementation to allow for other use cases.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8354897: Support Soft/Weak Reference in AOT cache (Enhancement - P3)

Reviewers

Contributors

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/24757/head:pull/24757
$ git checkout pull/24757

Update a local copy of the PR:
$ git checkout pull/24757
$ git pull https://git.openjdk.org/jdk.git pull/24757/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 24757

View PR using the GUI difftool:
$ git pr show -t 24757

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/24757.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Apr 18, 2025

👋 Welcome back iklam! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Apr 18, 2025

@iklam This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8354897: Support Soft/Weak Reference in AOT cache

Co-authored-by: Ioi Lam <[email protected]>
Reviewed-by: liach, eosterlund

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 21 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk
Copy link

openjdk bot commented Apr 18, 2025

@iklam The following labels will be automatically applied to this pull request:

  • core-libs
  • hotspot

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.

@iklam
Copy link
Member Author

iklam commented Apr 18, 2025

/author @macarte
/author @iklam

@openjdk
Copy link

openjdk bot commented Apr 18, 2025

@iklam
Setting overriding author to Mat Carter <[email protected]>. When this pull request is integrated, the overriding author will be used in the commit.

@openjdk
Copy link

openjdk bot commented Apr 18, 2025

@iklam
Setting overriding author to Ioi Lam <[email protected]>. When this pull request is integrated, the overriding author will be used in the commit.

@iklam
Copy link
Member Author

iklam commented Apr 18, 2025

/author @macarte
/contributor add @iklam

@openjdk
Copy link

openjdk bot commented Apr 18, 2025

@iklam
Setting overriding author to Mat Carter <[email protected]>. When this pull request is integrated, the overriding author will be used in the commit.

@openjdk
Copy link

openjdk bot commented Apr 18, 2025

@iklam
Contributor Ioi Lam <[email protected]> successfully added.

@iklam iklam marked this pull request as ready for review April 24, 2025 16:05
@openjdk openjdk bot added the rfr Pull request is ready for review label Apr 24, 2025
@mlbridge
Copy link

mlbridge bot commented Apr 24, 2025

Object referent = key.get();
if (referent == null) {
// We don't need this key anymore. Add to stale queue
((Reference)key).enqueue();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is enqueue necessary here? Afaik this map only uses the queue to be alert of member reference being garbage collected and then remove stale elements. Since at this stage this map is no longer used, maybe key.unused() is sufficient?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you are proposing. But enqueue is used to ensure that inactive references are pruned from the data structure so that dumping only includes active references. This way, the JVM that loads these objects create these references as active and hence follow the usual life cycle that every other reference does. I want to avoid having a new "special" life cycles for dumped references.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map is still being used (and will be stored into the AOT cache). The key no longer has a referent, so we need to remove the key from the map. Adding the key to the stale queue and calling removeStaleReferences() will accomplish this. key.unused() does not remove the key from the map.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I am fine with the rest of the core library changes.

@@ -1401,12 +1405,17 @@ class HeapShared::ReferentPusher: public BasicOopIterateClosure {
template <class T> void do_oop_work(T *p) {
oop obj = RawAccess<>::oop_load(p);
if (!CompressedOops::is_null(obj)) {
size_t field_delta = pointer_delta(p, _referencing_obj, sizeof(char));
int field_offset = pointer_delta_as_int((char*)p, cast_from_oop<char*>(_referencing_obj));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RawAccess oop load above should probably be a HeapAccess<ON_UNKNOWN_OOP_REF>::oop_load_at. By using the unknown oop ref and supplying the field offset, we allow GCs to infer the actual reference strength in the backend when loading and apply appropriate barriers for non-strong references, while also applying appropriate load barriers for conc GCs.

private static native int getCDSConfigStatus();
private static native void logLambdaFormInvoker(String line);


private static ArrayList<Object> keepAliveList;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth adding a comment stating this list should only be populated during an assembly phase? That it should be null in both training and production runs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added comments and asserts for clarification.

// 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 intent to evolve the implementation in the future by
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// We intent to evolve the implementation in the future by
// We intend to evolve the implementation in the future by

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

@fisk fisk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than reading the referent with the wrong accessor in the assert, I think this looks very good. Thanks for sorting this out in a clean and robust way.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Apr 29, 2025
Copy link
Member

@liach liach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core library changes look good.

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Apr 29, 2025
InstanceKlass* ik = InstanceKlass::cast(k);
ik->initialize(CHECK);

TempNewSymbol field_name = SymbolTable::new_symbol("N""ULL");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "N""ULL", not "NULL"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because a stand-lone word "NULL" will cause hotspot/jtreg/sources/TestNoNULL.java to fail. I have renamed the internal field to NULL_QUEUE to avoid the failure, per recommendation of @kimbarrett

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the explanation!

… failing hotspot/jtreg/sources/TestNoNULL.java
Copy link
Member

@xmas92 xmas92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. Just a few comments.

Comment on lines 231 to 233
if (ref2.get() == null) {
throw new RuntimeException("ref2.get() should not be null");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The referent of ref2 is never strong. If a GC occurs between makeRef2() and this check, we could hit this Exception. The test should either keep it strongly reachable until after this check or just not test wether it is reachable before the call to System.gc().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the check that was before the GC.

Comment on lines 94 to 95


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be guarded with INCLUDE_CDS_JAVA_HEAP somehow. I guess the cleanest way would be to use NOT_CDS_JAVA_HEAP_RETURN in aotReferenceObjSupport.hpp

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added NOT_CDS_JAVA_HEAP_RETURN to the declaration of init_keep_alive_objs_table. All the other functions are called only inside INCLUDE_CDS_JAVA_HEAP blocks, so I didn't change them to avoid cluttering the code.

runtimeSetup();
}

private static void runtimeSetup() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if you are planning an annotation or something to mark these "runtimeSetup" methods but each one will minimally need a comment so that it's clear to anyone touching this code that it may be called by the VM when loading the AOT cache.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the plan is to use annotation to mark the runtimeSetup and assemblySetup methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment above the new runtimeSetup() method in Reference.java. For the other existing occurrences, I plan to address them in a separate RFE, probably JDK-8342481


for (ReferenceKey<K> key : map.keySet()) {
Object referent = key.get();
if (referent == null) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be key.refersTo(null)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is immediately passed to CDS.keepAlive, two separate calls of refersTo and get may produce null on the second call, I presume.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I missed that referent was used later if non-null.

Copy link
Member

@liach liach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment addition requested by Alan.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Apr 30, 2025
@iklam
Copy link
Member Author

iklam commented Apr 30, 2025

Thanks everyone for the review. Passed tiers 1-4, as well as the ".*aot.jdkcache.*" tasks up to tier 10.
/integrate

@openjdk
Copy link

openjdk bot commented Apr 30, 2025

Going to push as commit 1ff7e81.
Since your change was applied there have been 23 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Apr 30, 2025
@openjdk openjdk bot closed this Apr 30, 2025
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Apr 30, 2025
@openjdk
Copy link

openjdk bot commented Apr 30, 2025

@iklam Pushed as commit 1ff7e81.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Comment on lines +309 to +314
// Called from JVM when loading an AOT cache
static {
runtimeSetup();
}

private static void runtimeSetup() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment was added in the wrong spot:

Suggested change
// Called from JVM when loading an AOT cache
static {
runtimeSetup();
}
private static void runtimeSetup() {
static {
runtimeSetup();
}
// Called from JVM when loading an AOT cache
private static void runtimeSetup() {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I will fix in an related PR #24979

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

9 participants