JavaFX and Listener Leaks

I am a little confused in JavaFx 8 and listener memory leak problem. The official doc says:

ObservableValue maintains a strong listener reference that will prevent collection of the receiver and may lead to a memory leak.

I would like to have an example where using a method ObservableValue<T> addListenercreates 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 an object ConfigurationPanecollects garbage, MyListenerdoes the object also collect garbage? I do not see the scenario in which

strong reference to the listener will not allow the listener to be garbage collected

PS I see other SO questions about this, but none of them helped me understand the problem.

Thank.

+4
1

, , , , , .

LeakingListener , :

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();
    }
}

ObservableValue , . :

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();
    }
}
+2

Source: https://habr.com/ru/post/1545739/


All Articles