Closing JavaFX tabs doesn't release memory fro

2019-08-07 15:14发布

I've been trying to figure out why a JavaFX based application I am working on uses up so much memory. I noticed memory went up each time I opened a new tab in the application, and wasn't being GC after I had closed the tab. I would keep opening and closing tabs until eventually it ran out of memory and crashed.

So I wrote and tried a very simple program and GUI. The code is below. What I found was that if I set the items in the TableView to null, cleared out the ArrayList, and instantiated a new ArrayList of the same variable name, only then it would GC.

Is this normal behavior for Java and/or JavaFX? Normal in that it won't "destroy" those objects in a tab once the tab is closed? Bug with Java 8/FX?

import java.util.ArrayList;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class TabTest extends Application {

    ArrayList<String> _list;

    @Override
    public void start(Stage primaryStage) {
        SplitPane split = new SplitPane();
        split.setOrientation(Orientation.VERTICAL);     
        HBox window = new HBox();       
        HBox top = new HBox(20);
        HBox bottom = new HBox(20);
        Group group = new Group();
        TabPane tabPane = new TabPane();
        Button btn = new Button("Create Tab");
        top.getChildren().add(btn);
        bottom.getChildren().add(tabPane);
        btn.setOnAction(new EventHandler<ActionEvent>() {            
            @Override
            public void handle(ActionEvent event) {
                createTabAndList(tabPane);
            }
        });
        split.getItems().addAll(top,bottom);
        window.getChildren().add(split);
        group.getChildren().add(window);
        Scene scene = new Scene(group);
        split.prefWidthProperty().bind(scene.widthProperty());
        split.prefHeightProperty().bind(scene.heightProperty());        
        primaryStage.setScene(scene);
        primaryStage.show();        
    }

    public static void main(String[] args) {
        launch(args);
    }

    public void createTabAndList(TabPane tabPane){
        _list = new ArrayList<String>();
        for(int i = 0; i < 10000000; i++){
            _list.add("Test Test Test");
        }
        TableView<String> tb1 = new TableView<String>();
        tb1.setItems(FXCollections.observableArrayList(_list));
        Tab tab = new Tab("Tab1");
        tab.setContent(tb1);
        tabPane.getTabs().add(tab);
        tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.SELECTED_TAB);        
        tab.setOnClosed(new EventHandler<Event>() {
            @Override
            public void handle(Event arg0) {
                tb1.setItems(null);
                _list.clear();
                _list = new ArrayList<String>();
            }
        });
    }
}

1条回答
女痞
2楼-- · 2019-08-07 15:44

I played with your sample a bit, out of curiosity. I worked on jdk1.8.60 on on Ubuntu. First, there is the global _list field, this remains in the memory forever, naturally, unless you specifically clean it. To simplify the things, I made the _list local. Then I removed the onClosed handler to see what happens. So the method createTabAndList was like this:

public void createTabAndList(TabPane tabPane){
    List<String> _list = new ArrayList<String>(); //_list is local now
    for(int i = 0; i < 10000000; i++){
        _list.add("Test Test Test");
    }
    TableView<String> tb1 = new TableView<String>();
    tb1.setItems(FXCollections.observableArrayList(_list));
    Tab tab = new Tab("Tab1");
    tab.setContent(tb1);
    tabPane.getTabs().add(tab);
    tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.SELECTED_TAB);        
    //tab.setOnClosed(new EventHandler<Event>() {
    //    @Override
    //    public void handle(Event arg0) {
    //        tb1.setItems(null);
    //        _list.clear();
    //        _list = new ArrayList<String>();
    //    }
    //});
}
  1. Newly started app consumes 8MB.
  2. 1 tab created - 63 MB
  3. 10 tabs created - 560 MB - there are 10 instances of the list now
  4. All 10 tabs closed - 62 MB

Indeed, JavaFX seems to keep the reference to the last active tab, including its content. There was non of your code on the reference path in hepdump, it was all in the JFX internals. I noticed it several times (tables, css processor), that JFX caches a lot of stuff that is not needed any more. But it gets typically cleaned up when after some time, or when the memory gets scarce. When I created and closed the 10 tabs several times again, it never consumed more that these 62 MB.

So, the answer is

  • Indeed, JFX remembers some extra stuff, but within reasonable limits. The memory does not grow infinitely by repeated opening and closing the tab.
  • If you really need such huge data models in the memory, like in your example (10M rows), then it makes sense to clear the tab on closing.
查看更多
登录 后发表回答