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