Search code examples
javajavafxmemorygarbage-collectionlistener

Does addListener in JavaFX get garbage collected when the ChangeListener is typed as a lambda?


I have realized that my project uses a lot of JavaFX ChangeListeners to keep track of certain conditions.

I am pretty sure that listeners do not get garbage collected when used with property.addListener(ChangeListener), but if I put my listeners in lambda form instead of declaring them first, will they still not get garbage collected?

Example:

        field.focusedProperty().addListener((obs, oldVal, newVal) -> {
          // Do stuff
          }
        });

On a side note, do all the listeners get cleared when I close the program or do they continue to persist?


Solution

  • Will the Listener be Garbage Collected?

    A listener you register with an observable will not be garbage collected so long as it is registered with the observable. Assuming, of course, there's a strong reference to the observable. This is because the observable keeps a strong reference to each listener registered with it. In fact, this can be the cause of memory leaks in your application if you're not careful. That's why we have:

    Weak Listeners

    To make your application more resistant to memory leaks you can use implementations of WeakListener. There are five public implementations provided for you:

    1. WeakInvalidationListener
    2. WeakChangeListener
    3. WeakListChangeListener
    4. WeakSetChangeListener
    5. WeakMapChangeListener

    These implementations wrap their corresponding listener and hold it in a WeakReference. That way the observable only has a strong reference to the weak listener and not the "real" listener. And that's why you have to keep a strong reference to the "real" listener yourself for as long as its needed, otherwise it will be garbage collected too soon.

    Example

    Here's an example of using a weak listener:

    import javafx.beans.value.ChangeListener;
    import javafx.beans.value.WeakChangeListener;
    import javafx.scene.Node;
    
    public class Foo {
      
      // strong reference to "real" listener
      private final ChangeListener<Boolean> focusListener =
          (obs, oldVal, newVal) -> {
            // do something...
          };
    
      private final Node node;
    
      public Foo(Node node) {
        this.node = node;
        node.focusedProperty().addListener(new WeakChangeListener<>(focusListener));
      }
    }
    

    If you need to be able to remove the weak listener at will then you'll need to keep a reference to it as well.

    When to use Weak Listeners

    You should use a weak listener when you can't guarantee the listener will be removed when no longer needed. This is especially important if the listener captures a reference to other objects (e.g. the Foo instance in the above example).

    Don't bother using a weak listener if the observable and listener are guaranteed to be garbage collected at relatively the same time. For example, if Foo had a property and adds a listener to said property itself (e.g. this.someProperty().addListener((...) -> {...})) then the property, and thus the listener, will be garbage collected when the Foo instance is garbage collected.

    Also, using weak listeners may be more effort than they're worth if:

    • Your application is trivial or relatively short-lived.
    • Your application is relatively static (i.e. does not continuously create and throw away objects that you register listeners with).

    If unsure, you can always go the non-weak route to start and only change to weak listeners if you profile a problem.

    Event Handlers

    Everything described here works the same for EventHandler and WeakEventHandler. Just note that the latter does not implement WeakListener.

    Lambda Expressions

    Does using a lambda expression to implement a listener (or handler) change anything? Not in any way relevant to this context. A lambda expression is simply an implementation of a functional interface. An instance created via a lambda expression is just an instance like any other.

    What Happens When the Application Exits?

    All memory allocated to the process is released back to the operating system when said process exits. That means all objects cease to exist in memory.