2014-06-23 63 views
4

我對JavaFx 8和監聽器內存泄漏問題有點困惑。官方doc說:JavaFX和監聽器內存泄漏

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.關於這個的問題,但沒有一個幫助我理解這個問題。

謝謝。

回答

2

這意味着存儲您的偵聽器的地圖不使用弱引用,而且您必須自行移除偵聽器以避免內存泄漏。

在下面LeakingListener對象的例子將永遠不會被釋放,雖然從場面被刪除相應的TextField:

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

爲什麼LeakingListener創建泄漏?問題是它對tf TextField有很強的參考性?那麼,在我上面的示例中,MyListener不會創建泄漏? – Giorgio

+0

對,你的例子很安全。只有在您動態地添加/刪除組件並在其中使用偵聽器時纔可能出現泄漏。 –

+0

好的。謝謝,但我仍然不明白爲什麼如果我動態地添加/刪除組件與偵聽器,我創建泄漏。 :-( – Giorgio