Закрытие вкладок JavaFX не освобождает память из ArrayLists и TableViews в этой вкладке
Я пытался выяснить, почему приложение на основе JavaFX, над которым я работаю, использует так много памяти. Я заметил, что память открывалась каждый раз, когда я открывал новую вкладку в приложении, и после закрытия этой вкладки я не был GC. Я продолжал бы открывать и закрывать вкладки, пока в конце концов не исчерпал бы память и не потерпел крах.
Поэтому я написал и попробовал очень простую программу и графический интерфейс. Код ниже. Что я обнаружил, так это то, что если я установлю элементы в TableView в null, очистим ArrayList и создам новый ArrayList с тем же именем переменной, только тогда это будет GC.
Это нормальное поведение для Java и / или JavaFX? Обычный в том, что он не будет "уничтожать" эти объекты на вкладке после закрытия вкладки? Ошибка с 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 ответ
Я немного поиграл с вашим образцом, из любопытства. Я работал на JDK1.8.60 на Ubuntu. Во-первых, есть глобальное поле _list, которое, естественно, останется в памяти навсегда, если вы не очистите его. Чтобы упростить вещи, я сделал _list локальным. Затем я удалил обработчик onClosed, чтобы посмотреть, что произойдет. Таким образом, метод createTabAndList был таким:
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>();
// }
//});
}
- Недавно запущенное приложение потребляет 8 МБ.
- Создана 1 вкладка - 63 МБ
- 10 созданных вкладок - 560 МБ - теперь в списке 10 экземпляров
- Все 10 вкладок закрыты - 62 МБ
Действительно, JavaFX, похоже, сохраняет ссылку на последнюю активную вкладку, включая ее содержимое. Не было вашего кода на пути ссылки в hepdump, все это было внутри JFX. Я заметил несколько раз (таблицы, процессор css), что JFX кэширует много вещей, которые больше не нужны. Но он обычно очищается, когда через некоторое время или когда памяти становится мало. Когда я несколько раз создавал и закрывал 10 вкладок, он никогда не потреблял больше, чем эти 62 МБ.
Итак, ответ
- Действительно, JFX запоминает некоторые дополнительные вещи, но в разумных пределах. Память не увеличивается бесконечно при повторном открытии и закрытии вкладки.
- Если вам действительно нужны такие огромные модели данных в памяти, как в вашем примере (10M строк), то имеет смысл очистить вкладку при закрытии.