RFR: 8373203: Genshen: Non-strong reference leak in old gen [v8]
Y. Srinivas Ramakrishna
ysr at openjdk.org
Mon Jan 12 21:41:27 UTC 2026
On Fri, 9 Jan 2026 17:52:35 GMT, William Kemper <wkemper at openjdk.org> wrote:
>> The generational mode for Shenandoah will collect _referents_ for the generation being collected. For example, if we have a young reference pointing to an old referent, that young reference will be processed after we finish marking the old generation. This presents a problem for discovery.
>>
>> When the young mark _encounters_ a young reference with an old referent, it cannot _discover_ it because old marking hasn't finished. However, if it does not discover it, the old referent will be strongly marked. This, in turn, will prevent the old generation from clearing the referent (if it even reaches it again during old marking).
>>
>> To solve this, we let young reference processing discover the old reference by having it use the old generation reference processor to do so. This means the old reference processor can have a discovered list that contains young weak references. If any of these young references reside in a region that is collected, old reference processing will crash when it processes such a reference. Therefore, we add a method `heal_discovered_lists` to traverse the discovered lists after young evacuation is complete. The method will replace any forwarded entries in the discovered list with the forwardee.
>>
>> This PR also extends whitebox testing support for Shenandoah, giving us the ability to trigger young/old collections and interrogate some properties of heaps and regions.
>
> William Kemper has updated the pull request with a new target base due to a merge or a rebase. The pull request now contains 23 commits:
>
> - Merge remote-tracking branch 'jdk/master' into fix-old-reference-processing
> - Merge remote-tracking branch 'jdk/master' into fix-old-reference-processing
> - Heal discovered lists for any young collection coincides with old marking
> - Configure thread local mark closure on delegated old reference processor
> - Merge remote-tracking branch 'jdk/master' into fix-old-reference-processing
> - Fix idiosyncratic white space in whitebox
>
> Co-authored-by: Stefan Karlsson <stefan.karlsson at oracle.com>
> - Sort includes
> - Heal old discovered lists in parallel
> - Fix comment
> - Factor duplicate code into shared method
> - ... and 13 more: https://git.openjdk.org/jdk/compare/f5fa9e40...abccb8b6
Thank you for explaining clearly here. I have some related questions below:
> Consider a situation where an old and young reference both point to an old referent:
>
> ```
> young_reference old_reference
> \ /
> \ /
> old_referent
> ```
...
>
> The young reference processor cannot _discover_ old referents because it will not have complete mark information when it comes time to process the discovered list.
Correct.
> By not discovering the old referent, the mark thread will mark and trace through the old referent.
Why? It should find the old referent to be in the old generation and leave it alone? Are you specifically talking about the case of bootstrap young that is seeding work for the old collection? In that case, it should mark old referent and leave it alone, but go no further. The old marking will find the referent marked and not clear it.
> Following the bootstrap, when old marking encounters `old_reference`, it sees that `old_referent` is already strongly marked and so similarly does not _discover_ it.
I would think that this is indeed the correct behaviour.
> We could continue to wait for `young_reference` to die or be promoted, but you can see that a single `young_reference` to any `old_referent` can prevent the reference from being cleared.
Yes, and that should be ok. Once reference and referent are both in old, it'll be cleared. I realize this is a consequence of the split local marking that each generation does. You are concerned that this will keep the referent perpetually around because of such "hand-over-hand" reference to it from each generation. One question is whether this ends up violating anything in the spec of java.lang.Reference. If so, which spec? Or is it a quality of implementation issue? Is there an example of that from an application/service where we see this?
>
> This PR has the young reference processor defer discovery to the old reference processor. This is simple enough, except that the young references now live in the old reference processor's discovered list. This list runs through the heap so these young references can be evacuated. This requires us to update references for the old processor's discovered list. Note that the list already updates the card table when young references are placed on the discovered list, so they become roots for subsequent young collections by virtue of being in the remembered set.
I am concerned about creating this extra coordination complexity unless there is a good practical reason to fix it in this manner.
-------------
PR Comment: https://git.openjdk.org/jdk/pull/28810#issuecomment-3740590447
More information about the shenandoah-dev
mailing list