The issue turned up in working on JDK-8244531. Failing test case:
@Test
public void testButtonBehaviorMemoryLeak() {
Button control = new Button();
WeakReference<BehaviorBase<?>> weakRef = new WeakReference<>(createBehavior(control));
assertNotNull(weakRef.get());
weakRef.get().dispose();
attemptGC(weakRef);
assertNull("behavior must be gc'ed", weakRef.get());
}
Problem missing removal of listener to control's focusedProperty. Actually, the behavior tries to but gets it wrong:
// in constructor
getNode().focusedProperty().addListener(this::focusChanged);
// in dispose
getNode().focusedProperty().removeListener(this::focusChanged);
the incorrect assumption here is that the lambda being remove is the _same_ instance as the one added (which isn't the case)
Fix is to have a field for the focusListener and add/remove that
InvalidationListener listener = this::focusChanged;
...
// in constructor
getNode().focusedProperty().addListener(focusListener);
// in dispose
getNode().focusedProperty().removeListener(focusListener);
Same issue with same reason and fix in ComboBoxBaseBehavior - will defer fix until JDK-8244531 is integrated (can then add cross-behavior test)