20

TL;DR: GC is eating my active bindings.

I have an app that was developed and successfully deployed using JavaFX 2.2 on Java 7.

When I upgraded/transitioned to JavaFX 8.0 (and Java 8), certain features would "mysteriously" stop working--mid application lifecycle--with no exceptions or other indications of erroneous state change. For example; buttons stop working, custom cell renderers stopped being applied, enabled/disabled state stopped getting updated.

After many hours of digging, I think I've tracked the problem down to what I understand to be some changes in JavaFX 8 and internal usage of javafx.beans.WeakListener to deal with memory leaks found in JavaFX 2.2. Basically, it seems that the bindings that I'm creating to manage data state dependency are getting garbage collected despite the fact that the Nodes they control are still active.

The problems most often seem to arise when I instantiate bindings using anonymous classes. Some but not all of my problems can be fixed by storing a reference to the binding as a class member, thereby keeping the GC from collecting it. I've even had whole controllers get GC'd because they were instantiated via FXML loading and never directly referenced (I now always stuff a reference to the controller in the parent node's userData property).

My problems are:

  1. associated bugs arise non-deterministically (or are at least a function of memory footprint).
  2. if bindings through anonymous classes should be avoided, it's a lot of work to find every instance in a large, existing code base to change it.
  3. even if I could find every instance, it clutters up the code immensely.

Frustratingly, I can't seem to find anything in the Oracle documentation that says "don't create bindings with anonymous classes", or any other guidelines for ensuring reliable use of bindings. A lot of the code examples out there use anonymous class bindings. Nor can I find any notes on how to properly update a JavaFX 2.2 app to JavaFX 8.

Any advice from people developing non-trivial JavaFX apps is greatly appreciated (I've been developing JavaFX 2.x apps for 3 years, and Swing apps for > 15 years, so this isn't exactly a n00b question).


Note: My question is similar to Clean JavaFX property listeners and bindings (memory leaks), but I want to know specifically and definitively how to use complex bindings and ensure they won't be garbage collected at random times, without resorting to polluting classes with references to every instance.

bruno
  • 2,213
  • 1
  • 19
  • 31
metasim
  • 4,793
  • 3
  • 46
  • 70
  • 2
    can you elaborate a bit on "changes in JavaFX 8 and internal usage of javafx.beans.WeakListener" as I'm not aware of those (concededly, refused to look too deep into fx pre 8 ;-) - in my experience garbage collected bindings always were (and still are) a nuisance ... – kleopatra Jan 29 '15 at 14:19
  • This is very strange behavior. Could you show an example of the implementation of your listeners? – Angelo Alvisi Jan 29 '15 at 14:26
  • Having a hard time re-finding change set docs from Oracle on the issue (lots of dead links). Here are two threads on the issue pre JavaFX 8.: https://javafx-jira.kenai.com/browse/RT-32087, https://community.oracle.com/thread/2396063. – metasim Jan 29 '15 at 14:42
  • If no one else posts similar issues I'll attempt to boil it down to a smaller example, but it's a multi-10K SLOC application and not always predictable when it happens. I do know that a) didn't happen in JavaFX 2.2, and b) adding class fields to every instantiated binding fixes the issue. – metasim Jan 29 '15 at 14:44
  • 1
    For those looking for a simple example, see [this question](http://stackoverflow.com/questions/23785816/javafx-beans-binding-suddenly-stops-working). What you want to do is make the lifecycle of any binding the same as any UI element that relies on it (which will be tricky). – James_D Jan 29 '15 at 14:58
  • @James_D if your example indeed describes the context here I'm wondering why OPs code does work in fx2.x - have been raging about that exact behaviour since day 1 of 2.x ;-) – kleopatra Jan 29 '15 at 15:12
  • 1
    Yes, just tested, and in that other question the "lost bindings" issue seems to arise in both JavaFX 2.2 and JavaFX 8.... – James_D Jan 29 '15 at 15:56
  • 1
    Admittedly, the fact that my code works in 2.x might be a red herring; GC behavior and memory footprint might be different enough in Java 8 to make a difference. – metasim Jan 29 '15 at 16:03
  • 1
    It doesn’t surprise me that you don’t find such warnings in the documentation. That would imply that somebody thought about the implication of weak listeners and, well, if somebody thought about it, the idea of weak listeners was dropped immediately. But hey, I wish it was the only bad idea in the JavaFX design… – Holger Jan 29 '15 at 17:19
  • 1
    This behaviour changed slightly even between intermediate Java versions 8u11 and 8u20: see my comment at https://gist.github.com/jewelsea/5375786 for additional background info. – jewelsea Jan 29 '15 at 17:45
  • 1
    You do not need to store all the intermediate bindings, just the endpoints, that is, in `a.add(1).multiply(2)`, you don't need to store a reference to `a.add(1)`, just a reference to the resulting binding. I know that is still annoying. – Tomas Mikula Jan 30 '15 at 03:07
  • 1
    In the snapshot version of [ReactFX](https://github.com/TomasMikula/ReactFX) there is the type `Val` which is like `ObservableValue` and `Binding` at the same time, but does not use weak listeners. Instead, the "binding" is disposed automatically when the last listener is removed (and reconnected again if listener is added). That is, in a chain like `a.map(x -> x+1).filter(x -> x%5==0).orElseConst(5)`, when the last listener is removed, all the intermediate bindings are automatically disposed, even without weak listeners. It is not yet released or documented, but it is there and pretty stable. – Tomas Mikula Jan 30 '15 at 03:08
  • Thanks for the tip about ReactFX. I've tried [EasyBind](https://github.com/TomasMikula/EasyBind) in the past, but ReactFX looks more concise. – metasim Jan 30 '15 at 20:49

1 Answers1

3

The WeakEventHandler is -supposed- to allow GC of the listener object (if it is not otherwise referenced) and simply stop working at that time. As you have discovered, this means you have to reference the handler as long as you need it to keep triggering. This requirement is more or less independent of whether you use an anonymous class or not; it would fail in the same way if you used a normal class.

There is no possible way to "automatically" determine that a certain event will not be triggered in the future anymore, which is essentially what a feature request to "fix" this problem would require. If you don't want anything GC'd you can simply add all the anonymous listeners to a list stored as a static variable somewhere. If you want GC to work (and eventually you will), you will have to control it by maintaining references only when they are needed and releasing them when no longer.

Atsby
  • 2,277
  • 12
  • 14
  • This question has 19 votes whereas the sole answer only has three votes despite being accepted as a solution. This seems disproportionate and makes me doubt this answer. Have a lot of people found the question good but the sole answer not so good? Atsby, five years have passed since this answer (and apparently your last presence here too), are you aware yourself of any weaknesses in your answer? – pateksan Dec 08 '20 at 00:17