Закрытие вкладок 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>();
    //    }
    //});
}
  1. Недавно запущенное приложение потребляет 8 МБ.
  2. Создана 1 вкладка - 63 МБ
  3. 10 созданных вкладок - 560 МБ - теперь в списке 10 экземпляров
  4. Все 10 вкладок закрыты - 62 МБ

Действительно, JavaFX, похоже, сохраняет ссылку на последнюю активную вкладку, включая ее содержимое. Не было вашего кода на пути ссылки в hepdump, все это было внутри JFX. Я заметил несколько раз (таблицы, процессор css), что JFX кэширует много вещей, которые больше не нужны. Но он обычно очищается, когда через некоторое время или когда памяти становится мало. Когда я несколько раз создавал и закрывал 10 вкладок, он никогда не потреблял больше, чем эти 62 МБ.

Итак, ответ

  • Действительно, JFX запоминает некоторые дополнительные вещи, но в разумных пределах. Память не увеличивается бесконечно при повторном открытии и закрытии вкладки.
  • Если вам действительно нужны такие огромные модели данных в памяти, как в вашем примере (10M строк), то имеет смысл очистить вкладку при закрытии.
Другие вопросы по тегам