Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8096196

Memory Leak in TabPane When Closing Tabs is not Animated

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Cannot Reproduce
    • Icon: P3 P3
    • 8u20
    • 8
    • javafx
    • Java 1.8.0ea b124

      If tabs are closed when the close tab animation is set to "none" the TabPaneSkin holds a reference to the closed tab that is never removed for the life of the TabPane.

      I believe I've tracked this down to the removeTabs method in TabPaneSkin (lines 244 -264). When a tab is removed with no animation the method calls handleClosedTab() and then adds the tab to a "closedTab" map when in fact the handleCloseTab() method is the one that removes the tab from the map.

      Even in the case of the tab close being animated this method performs it actions a little out of sequence. It counts on the animation not finishing immediately since it actually starts the animation and then adds the tab to the same closedTab map a few lines later. It is of course the end of the animation that is also supposed to remove the tab from this map.

      Run the provided test case to observe the memory leak.

      1. Press the Generate Tabs a couple times and notice the tabs are fully replaced each time.

      2. Press the Print Report button and observe the console. You should see all but 40 tabs are GC'ed. It seems the skin keeps references to the recently closed tabs but in this case its never more than 20 extra refs.

      3. Turn off the tab animation by deselecting the checkbox.

      4. Press Generate Tabs a couple more times and then Print Report. You will notice now that with animation turned off the number of referenced tabs will always grow by 20 with each press of the generate button.

      Ideally the Print Report button would only list 20 tabs which would correspond to the 20 currently in the tabpane.


      ******************************************* Test Class *****************************************************
      import java.lang.ref.WeakReference;
      import java.util.ArrayList;
      import java.util.HashMap;
      import java.util.HashSet;
      import java.util.List;
      import java.util.Map.Entry;
      import java.util.Set;

      import javafx.application.Application;
      import javafx.event.ActionEvent;
      import javafx.event.EventHandler;
      import javafx.geometry.Insets;
      import javafx.geometry.Pos;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.CheckBox;
      import javafx.scene.control.Label;
      import javafx.scene.control.Tab;
      import javafx.scene.control.TabPane;
      import javafx.scene.layout.BorderPane;
      import javafx.scene.layout.HBox;
      import javafx.scene.layout.Region;
      import javafx.scene.layout.StackPane;
      import javafx.stage.Stage;

      public class TabPaneTest extends Application {
           
         final Set<WeakReference<Object>> refs_ = new HashSet<>();

         @Override public void start(final Stage primaryStage) throws Exception {

            primaryStage.centerOnScreen();
            primaryStage.setHeight(350);
            primaryStage.setWidth(500);
            
            final TabPane tabPane = new TabPane();
              
            final CheckBox checkBox = new CheckBox("Use Animation");
            checkBox.setSelected(true);
            checkBox.setOnAction(new EventHandler<ActionEvent>() {
               public void handle(ActionEvent event) {
                  tabPane.setStyle(checkBox.isSelected() ? "" :
                     "-fx-open-tab-animation: none;"
                        + "-fx-close-tab-animation: none;" );
               }
            });
              
            Button generateButton = new Button("Generate Tabs");
            generateButton.setOnAction(new EventHandler<ActionEvent>() {
               public void handle(ActionEvent event) {
                  List<Tab> newTabs = new ArrayList<Tab>();
                  for( int i = 0; i < 20; i++ ) {
                     Tab tab = new Tab("" + i);
                     Label content = new Label("" + i);
                     content.setStyle("-fx-font-size: 3em");
                     tab.setContent( new StackPane(content) );
                     newTabs.add( tab );
                     refs_.add(new WeakReference<Object>(tab));
                  }
                  tabPane.getTabs().setAll(newTabs);
               }
            });
            generateButton.fire(); // initial tabs
              
            Button printButton = new Button("Print References");
            printButton.setOnAction(new EventHandler<ActionEvent>() {
               public void handle(ActionEvent event) {
                  Runtime.getRuntime().gc();
                  Set<WeakReference<Object>> collected = new HashSet<>();
                  for ( WeakReference<Object> ref: refs_ ) {
                     if ( ref.get() == null ) {
                        collected.add(ref);
                     }
                  }
                  refs_.removeAll(collected);
                  System.out.println("*********************************************");
                  System.out.println(String.format("%d objects collected.",
                        collected.size()));
                  System.out.println(String.format("%d objects remain.",
                        refs_.size()));
                  HashMap<String, Integer> remainingMap = new HashMap<>();
                  for ( WeakReference<Object> ref: refs_ ) {
                     Object ob = ref.get();
                     if ( ob != null ) {
                        String className = ob.getClass().getSimpleName();
                        Integer count = remainingMap.get(className);
                        count = count == null ? 1 : count + 1;
                        remainingMap.put(className, count);
                     }
                  }
                  for ( Entry<String, Integer> entry: remainingMap.entrySet() ) {
                     System.out.println(
                           " " + entry.getValue() + " " +entry.getKey() +" remain");
                  }
                  System.out.println("*********************************************");
               }
            });
            
            HBox box = new HBox(10, checkBox, generateButton, printButton );
            box.setMaxWidth(Region.USE_PREF_SIZE);
            box.setAlignment(Pos.BASELINE_LEFT);
            box.setPadding( new Insets(10));
            BorderPane borderPane = new BorderPane(tabPane, null, null, box, null);
            BorderPane.setAlignment(box, Pos.CENTER);
            primaryStage.setScene( new Scene( borderPane ));
              
            primaryStage.show();

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

      }

            jgiles Jonathan Giles
            csmithjfx Charles Smith (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported: