JavaFX和侦听器内存泄漏

时间:2014-06-23 18:14:35

标签: memory memory-leaks garbage-collection listener javafx-8

我对JavaFx 8和监听器内存泄漏问题感到有些困惑。官方doc说:

  

ObservableValue存储对侦听器的强引用,这将阻止侦听器被垃圾回收并可能导致内存泄漏。

我想举例说明ObservableValue<T> addListener方法的使用会造成内存泄漏。

例如,如果我有这样的类:

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

我可以获得内存泄漏吗?当一个ConfigurationPane对象被垃圾收集时,MyListener对象也被垃圾收集了吗?我无法看到

的情况
  

对侦听器的强引用将阻止侦听器被垃圾回收

P.S。我看到其他S.O.关于这一点的问题,但这些都没有帮助我理解这个问题。

感谢。

1 个答案:

答案 0 :(得分:2)

这意味着存储侦听器的map不使用弱引用,您必须自己删除侦听器以避免内存泄漏。

在下面的示例中,尽管从场景中删除了相应的TextFields,但永远不会释放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();
    }
}

如果ObservableValueweak reference存储到侦听器,则不会出现问题。它可以通过下一个例子来模仿:

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