Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Tuesday, August 18, 2015

InvalidationListener Not Called

The JavaFX documentation warns against a memory leak if you don't de-register your InvalidationListeners.  If you don't plan on explicitly removing the listeners, consider wrapping your listeners in WeakInvalidationListeners. Also mentioned in the Javadoc, you need to keep a reference to avoid the listener being garbage collected before it's even used.  If you find your InvalidationListener not taking effect, don't abandon the weak references.  Read on.

I added an InvalidationListener to a TextField that fires when its textProperty changes.  As instructed by the documentation, I wrapped the call in a WeakInvalidationListener since I didn't plan on making a removeListener() call later.  This block of code appears in an @FXML initialize() method.

tfSourceFile.textProperty().addListener(new WeakInvalidationListener(
    new InvalidationListener() {
        @Override
        public void invalidated(Observable observable) {
           System.out.println("here");
           needsSave.set(true);
        }

        @Override
        public void finalize() {
          System.out.println("reclaimed");
        }
   }
));

I overrode the finalize() method to demonstrate that the InvalidationListener object is reclaimed immediately.  When my app starts up and loads the FXML that defines tfSourceFile, I see "reclaimed".

Removing the WeakInvalidationListener wrapper entirely fixes this.  However, you may walk into a memory leak.  So, it's a good programming practice to help Java untangle bidirectional relationships that might result in a cycle preventing objects from being reclaimed.  This seems to me to be especially important as JavaFX apps adopt binding.

So, I moved the anonymous InvalidationListener class out from the addListener() call and into the class definition as a private final field.  The addListener() call wraps up this field with the WeakInvalidationListener.  A Lambda makes the syntax look great.

In the class definition

private final InvalidationListener needsSaveListener = (evt) -> needsSave.set(true);

addListener() call in the initialize method.

tfSourceFile.textProperty().addListener(new WeakInvalidationListener(needsSaveListener));

Memory leaks are especially difficult to troubleshoot.  As developers closing tickets, we may bring an app up, test something, and shut the app down.  This is a very different usage pattern than the end user who may run the app for hours.  The best way to fend of memory leaks in your app is using solid coding techniques verified by examining with VisualVM.  Unfortunately, some developers might bail on using classes like WeakInvalidationListener if they forget about keeping a reference and see things working correctly by stuffing a the unwrapped InvalidationListener in addListener().  This post draws out the listener into a class field and uses a Lambda to make the syntax concise.

3 comments:

  1. Love your blog and nice post indeed! As I am fairly new to advanced programming and new to JavaFX, am I right by reading documentation that we should also use WeakChangeListeners over ChangeListeners?

    ReplyDelete
    Replies
    1. Yes. I always use WeakChange / InvalidationListeners unless I plan on manually disposing of them.

      We're taught early on that Java garbage collection will handle all memory management. However, you can still get memory leaks. The weak listeners help the JVM to untangle an object graph that is a cycle. Weak listeners allow one side of a reference holder to defer to the other when an object graph is freed, breaking the cycle.

      Delete
    2. Great, thanks for your kind and quick confirmation. Will integrate that habit from now on.

      Delete