RFR: 8290040: Provide simplified deterministic way to manage listeners [v2]
John Hendrikx
jhendrikx at openjdk.org
Fri Sep 2 03:50:53 UTC 2022
On Thu, 1 Sep 2022 17:38:39 GMT, Nir Lisker <nlisker at openjdk.org> wrote:
>> Edit: I missed some nuance, below is the correct version.
>>
>> There are three objects involved here: the parent observable, the new observable and the conditional.
>>
>> What `when` should achieve is to make the new observable and conditional easy to garbage collect when the conditional is `false` (the parent observable is considered to be long lived so GC doesn't really apply there). The conditional and new observable refer to each other so they must have a similar life cycle.
>>
>>> What happens if they are GC'd and the conditional becomes `true` later?
>>
>> This can't happen, the conditional refers to the new observable, their life cycles are tied to each other.
>>
>> Strong references look like this when conditional is `true`:
>>
>> conditional <--> new observable <--> parent observable
>>
>> When it is `false`:
>>
>> conditional <--> new observable --> parent observable
>>
>> Conditional must have a similar life cycle as new observable if your purpose is to use `when` to break strong references to allow for GC.
>
> If I have a (dumb) method
>
>
> void someMethod(ObservableValue<String> longLivedProperty) {
> ObservableValue<Boolean> condition = new SimpleBooleanProperty(true);
> ObservableValue<String> whenProperty = longLivedProperty.when(condition)
> }
>
>
> then shouldn't `condition` and `whenProperty` be eligible for GC even when `condition` holds `true`? If not, do I not get a memory leak because I can't change `condition` to `false` to allow garbage collection?
Well, this specific example, yes, they'll be both eligible for GC, but that's because you're not observing `whenProperty` anywhere. When not observed, it won't create a listener on `longLivedProperty`.
Assuming that `whenProperty` is observed and with `condition` being always `true` it is basically the same as:
ObservableValue<String> whenProperty = longLivedProperty.map(x -> x);
As long as `whenProperty` is observed, `longLivedProperty` has a direct reference to it (via listener). With `when` however, the `condition` can be used to (temporarily or permanently) break this reference. You could set it to `false` in `dispose` for example, triggering the unregistration of the listener on `longLivedProperty` (for multiple properties at once if desired):
ObservableValue<Boolean> active = new SimpleBooleanProperty(true);
MySkin() {
getSkinnable().textProperty().when(active).addListener(this::updateSkinStuffs);
getSkinnable().fontProperty().when(active).addListener(this::updateSkinStuffs);
getSkinnable().wrapTextProperty().when(active).addListener(this::updateSkinStuffs);
}
void dispose() {
active.set(false); // kills all listeners/bindings/etc in a single line
}
Or the other case I really like:
label.textProperty().bind(longLivedProperty.when(label::isShownProperty));
As long as `label` is showing, it takes updates from `longLivedProperty`; if the screen is closed, it detaches, and (assuming all such bindings are nicely detached) the label (and the rest of the UI it may be part of) can be GC'd without problem.
If you kept a reference to the UI (containing that label), then you also kept a reference to the label, to its text property, and to that binding with then `when` in it -- making that UI visible again restores the listener on `longLivedProperty` and the label immediately receives whatever the latest value is in that property if it changed while the UI was invisible.
-------------
PR: https://git.openjdk.org/jfx/pull/830
More information about the openjfx-dev
mailing list