-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8362289: [macOS] Remove finalize method in JRSUIControls.java #26331
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
base: master
Are you sure you want to change the base?
Conversation
👋 Welcome back prr! A progress list of the required criteria for merging this PR into |
@prrace 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:
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 18 new commits pushed to the
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 |
} | ||
|
||
public synchronized void dispose() { | ||
if (cfDictionaryPtr == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
although the constructor throws an exception if it can't initialize this and also the native method checks for 0 too,
I decided to leave this existing check out of an abundance of caution.
It ensures that the method is safe to be called twice.
Webrevs
|
@@ -109,16 +113,29 @@ public JRSUIControl(final boolean flipped){ | |||
flipped = other.flipped; | |||
cfDictionaryPtr = getCFDictionary(flipped); | |||
if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a side topic to discuss: In the past I tried to look into this pattern and figure out is it possible for the GC to start cleaning the object after we allocate the native resource but before we register it with Disposer.addRecord. In such a case, the cleanup might never be executed.
Perhaps we should consider adding a reachabilityFence at the end of Disposer.addRecord, for both the DisposerRecord and the referent, to ensure they remain reachable. Similarly how it was done for cleaner:
https://hg.openjdk.org/jdk9/jdk9/jdk/rev/1f33e517236e
Or may be we can just use cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is even a bug for Cleaner that the reachabilityFence should be moved to the end of register:
https://bugs.openjdk.org/browse/JDK-8291867
If there are no objectiosn I'll add it to the Disposer.addRecord in a separate bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would be OK, meaning probably harmless. But I don't know if this case actually requires it.
Where I've seen a problem like this a Java object is used at an earlier point in the method and not again and so becomes eligible to be freed, but some native resource it held is needed later but is already freed along with its no longer references holder.
Here, if this object (the JRSUIControl) becomes unreachable during construction, and so the referent and disposer are also unreachable outside this object, they are still going to be live until we reach code in the constructor which uses them and since the referent and the disposer are passed into addRecord they'll be live there until stored.
But these cases are subtle, maybe I'm missing something. Can you expand on the scenario ?
Or maybe there's some other patterns where we use Disposer that it is needed ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand why reachabilityFence is used here. That code first registers the object in the cleanup queue and then continues executing the rest of the constructor. As far as I understand, the object could be collected between the registration and the end of the constructor so the cleaner will be called on a partially constructed object.
If that assumption is correct then in the change above the code:
nativeMap = new HashMap<Key, DoubleValue>();
changes = new HashMap<Key, DoubleValue>();
might never be executed and the native disposer will be called before, not an issue in this particular case but good to know.
Or I missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's right. At first I thought you were referring to the constructor of the thing that wants to use the cleaner, but you mean the constructor of the Cleanable itself may be still under construction when the referent is collected and installing the reachabilityFence at the end of the constructor prevents that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to compare how Cleaner and Disposer are used. I found that in most cases Cleaner uses this
as the object to track. But Disposer often uses a separate object like disposerReferent or some kind of anchor. This patch is one example. In some places we even replaced this with a different object. You can see that in the following RFR:
https://bugs.openjdk.org/browse/JDK-8030099
https://mail.openjdk.org/pipermail/swing-dev/2015-October/005053.html
I think I found the reason: https://bugs.openjdk.org/browse/JDK-8071507
In older versions of the JDK the Disposer had to use phantom references. Phantom references allow native cleanup to happen after the object is 100% no longer reachable. But this had a problem, the object stayed in memory until the reference was added to the queue and the code called clear.
To avoid keeping large AWT or Java2D objects in memory for too long the code used small helper objects as referents instead of using the real object.
Now the problem described in JDK-8071507 is fixed. So maybe we no longer need to use small disposer referents. And you can check that by the test from the description of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is possible, but I'd prefer to keep this pattern. This way It is crystal clear that nothing to do with reference queues etc are delaying the GC from immediately reclaiming the memory.
Honestly, it probably matters not so much for JRSUIControls, and even less so for CGraphicsEnvironment, but may matter for some other cases.
A later follow-up fix could go change a whole bunch of these cases at once and we'd then have a good reference (pun intended) as to whether any issues showed up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good
@prrace this pull request can not be integrated into git checkout jrsuicontrols
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend adding a reachabiltyFence()
to Disposer.addRecord()
, as @mrserb suggests.
Unexpected unreachability is a subtle problem with GC-assisted APIs - finalizers, Cleaner
, and even direct use of References
& ReferenceQueues
. In particular, the VM can decide than object is unreachable even when a method on that object is still running.
For this reason, our standard recommendation is that any instance method that accesses the cleanable state (in this case, cfDictionaryPtr
) should be enclosed with a try{...}finally{ reachabilityFence(); }
block.
Synchronized methods might be OK without, but the following methods are not synchronized, and access cfDictionaryPtr
:
getHitForPoint()
getPartBounds()
getScrollBarOffsetChange()
sync()
Unfortunately this is actually true, in awt we even have a special class CFRetainedResource which maintains a locks around the native pointer and prevents its usage after de-allocation(intentional or via finalize). It was implemented as part of JDK-8164143. I can migrate the current API of DIsposer to the something similar to CFRetainedResource as a follow up bug. The current one did not introduce the new issues, since implementation via finalize has the same bug. |
Sounds good. True, the issue already existed with the finalizer. |
What does that mean ? CFRetainedResource is extended by a lot of classes and it is hard to figure out how correctly it is used. |
I do not see any scenario in which this is needed. |
I have at least filed the [enhancement] bug on this : https://bugs.openjdk.org/browse/JDK-8363886 |
Eliminate a finalize() method in the Swing macOS implementation.
I tested that the dispose method is being called by running this small test in combination with some 'println' statements in the source code (now removed)
I also monitored the process size using 'top'.
And when I commented out the native call that did the free and re-tested I saw process size grow.
Turning the above into a useful regression test doesn't seem possible to me.
Limiting Java heap is pointless (and I did use -Xmx20m) since it is a native resource that is to be freed and failure to dispose won't show up as a problem without taking down the machine.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/26331/head:pull/26331
$ git checkout pull/26331
Update a local copy of the PR:
$ git checkout pull/26331
$ git pull https://git.openjdk.org/jdk.git pull/26331/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 26331
View PR using the GUI difftool:
$ git pr show -t 26331
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/26331.diff
Using Webrev
Link to Webrev Comment