Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX and listener memory leaks

I'm a bit confused about JavaFx 8 and the listener memory leak problem. The official doc says:

The ObservableValue stores a strong reference to the listener which will prevent the listener from being garbage collected and may result in a memory leak.

I would like to have an example where the usage of ObservableValue<T> addListener method create a memory leak.

For example, if I have a class like this:

public class ConfigurationPane extends AnchorPane {
    @FXML
    private Label titleLabel;

    public ConfigurationPane () {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("view/ConfigurationPane .fxml"));
    fxmlLoader.setRoot(this);
    fxmlLoader.setController(this);
    try {
        fxmlLoader.load();
    } catch (IOException e) {
          e.printStackTrace();
      }
}

    @FXML
    private void initialize() {
        titleLabel.sceneProperty().addListener(new MyListener());
    }
}

Can I get memory leaks? When a ConfigurationPane object is garbage collected, the MyListener object is garbage collected too? I'm not able to see a scenario where

a strong reference to the listener will prevent the listener from being garbage collected

P.S. I see other S.O. questions about this but none of these helped me to understand the problem.

Thanks.

like image 468
Giorgio Avatar asked Oct 20 '22 06:10

Giorgio


1 Answers

It means that map which store your listener is not using weak references, and you have to remove listeners youself to avoid memory leaks.

In the example below LeakingListener objects will never be freed although corresponding TextFields being removed from scene:

public class LeakListener extends Application {

    private static class LeakingListener implements InvalidationListener {

        private final TextField tf;
        private final int[] placeHolder = new int[50000]; // to simplify monitoring

        public LeakingListener(TextField tf) {
            this.tf = tf;
        }

        public void invalidated(Observable i) {
            tf.setText(tf.getText() + ".");
        }
    }

    @Override
    public void start(Stage primaryStage) {
        final Pane root = new VBox(3);

        final Button btnType = new Button("Type in all");

        Button btnAdd = new Button("Add");
        btnAdd.setOnAction((e) -> {
            TextField tf = new TextField();
            root.getChildren().add(tf);
            // memory leaking listener which never gets cleaned
            btnType.armedProperty().addListener(new LeakingListener(tf));
        });

        Button btnRemove = new Button("Remove");
        btnRemove.setOnAction((ActionEvent e) -> {
            // find random TextEdit element
            Optional<Node> toRemove = root.getChildren().stream().filter((Node t) -> t instanceof TextField).findAny();
            // if any, and remove it
            if (toRemove.isPresent()) {
                root.getChildren().remove(toRemove.get());
            }
        });

        Button btnMemory = new Button("Check Memory");
        btnMemory.setOnAction((e) -> {
            System.gc();
            System.out.println("Free memory (bytes): " + Runtime.getRuntime().freeMemory());
        });

        root.getChildren().addAll(btnAdd, btnRemove, btnType, btnMemory);
        Scene scene = new Scene(root, 200, 350);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

If ObservableValue stores weak reference to a listener, you wouldn't have a problem. It can be mimicked by next example:

public class LeakListener extends Application {

    private static class NonLeakingListener implements InvalidationListener {

        // we need listener to don't hold reference on TextField as well
        private final WeakReference<TextField> wtf;
        private final int[] placeHolder = new int[10000];

        public NonLeakingListener(TextField tf) {
            this.wtf = new WeakReference<>(tf);
        }

        public void invalidated(Observable i) {
            if (wtf.get() != null) {
                wtf.get().setText(wtf.get().getText() + ".");
            }
        }
    }

    @Override
    public void start(Stage primaryStage) {
        final Pane root = new VBox(3);

        final Button btnType = new Button("Type in all");

        // Here is rough weak listeners list implementation
        WeakHashMap<TextField, NonLeakingListener > m = new WeakHashMap<>();
        btnType.armedProperty().addListener((e)-> {
            for (TextField tf : m.keySet()) {
                m.get(tf).invalidated(null);
            }
        });


        Button btnAdd = new Button("Add");
        btnAdd.setOnAction((e) -> {
            TextField tf = new TextField();
            root.getChildren().add(tf);
            m.put(tf, new NonLeakingListener(tf));
        });

        Button btnRemove = new Button("Remove");
        btnRemove.setOnAction((e) -> {
            // find random TextEdit element
            Optional<Node> toRemove = root.getChildren().stream().filter((Node t) -> t instanceof TextField).findAny();
            // if any, and remove it
            if (toRemove.isPresent()) {
                root.getChildren().remove(toRemove.get());
            }
        });

        Button btnMemory = new Button("Check Memory");
        btnMemory.setOnAction((e)-> {
            System.gc();
            System.out.println("Free memory (bytes): " + Runtime.getRuntime().freeMemory());
        });

        root.getChildren().addAll(btnAdd, btnRemove, btnType, btnMemory);
        Scene scene = new Scene(root, 200, 350);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}
like image 115
Sergey Grinev Avatar answered Oct 24 '22 10:10

Sergey Grinev