2015-08-10 1 views
1

Я пытался выяснить, почему приложение на основе JavaFX, над которым я работаю, использует столько памяти. Я заметил, что память поднималась каждый раз, когда я открывал новую вкладку в приложении, и не был GC после того, как я закрыл вкладку. Я бы продолжал открывать и закрывать вкладки, пока в конце концов не закончилось память и не разбился.Закрытие вкладок JavaFX не освобождает память из ArrayLists и TableViews на этой вкладке

Итак, я написал и попробовал очень простую программу и графический интерфейс. Код ниже. Я обнаружил, что если я установил элементы в 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

Как правило, Java не уничтожает * ничего *, о котором вы все еще имеете ссылку. – RealSkeptic

+0

Зачем мне/приложение иметь ссылку на него, если вкладка закрыта? – user1795832

+0

Поскольку элемент GUI визуально доступен или нет, он не имеет ничего общего с объектами, которые его создают. Что делать, если вы хотите, например, выполнить «закрытие закрытой вкладки»? – RealSkeptic

ответ

4

Я играл с вашим образцом немного, из любопытства. Я работал над 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. Недавно начал приложение потребляет 8MB. Вкладка
  2. 1 создано - 63 MB
  3. 10 вкладок, созданных - 560 МБ - есть 10 экземпляров списка закрыты сейчас
  4. Все 10 Вкладки - 62 MB

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

Итак, ответ

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

Да, обновление 60, казалось, прояснило много проблем с памятью, которые у меня были. – user1795832

Смежные вопросы