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

TableView memory leak when scrolling and V-resizing a TableView without adding/removing items

    Details

      Description

      Scrolling and vertical resizing results in VirtualFlow.sheet growing. VirtualFlow.sheet can only be cleared through TableViewSkin.updateRowCount()

      TableViewSkin.updateRowCount() is only called when TableViewSkin.rowCountDirty == true

      TableViewSkin.rowCountDirty is only set to true if table items change or if TableViewSkin.refreshView() is called.

      So, when the items do not change VirtualFlow.sheet can only be cleared through TableViewSkin.refreshView().

      TableViewSkin.refreshView() is called when the "TableView.refresh" property change but this property does not change when scrolling and resizing a TableView.


      The example below will animate the max height of the TableView and print the VirtualFlow.sheet child count to the console:

      ---------------------------------------------------------------------------------------
      package fxtest;

      import javafx.animation.KeyFrame;
      import javafx.animation.KeyValue;
      import javafx.animation.Timeline;
      import javafx.animation.TimelineBuilder;
      import javafx.application.Application;
      import javafx.beans.InvalidationListener;
      import javafx.beans.Observable;
      import javafx.beans.property.DoubleProperty;
      import javafx.beans.property.ReadOnlyStringWrapper;
      import javafx.beans.property.SimpleDoubleProperty;
      import javafx.beans.value.ObservableValue;
      import javafx.event.ActionEvent;
      import javafx.event.EventHandler;
      import javafx.scene.Group;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.TableColumn;
      import javafx.scene.control.TableColumn.CellDataFeatures;
      import javafx.scene.control.TableView;
      import javafx.scene.layout.Priority;
      import javafx.scene.layout.Region;
      import javafx.scene.layout.VBox;
      import javafx.scene.layout.VBoxBuilder;
      import javafx.stage.Stage;
      import javafx.util.Callback;
      import javafx.util.Duration;

      import com.sun.javafx.scene.control.skin.VirtualFlow;

      /**
       * When the selected row is removed the selection is rendered on another row but there is no notification
       */
      public class TableVFlowMemLeak extends Application {
         static class RowItem {
            public final String name;
            public DoubleProperty val = new SimpleDoubleProperty();

            public RowItem(final String n, final double p) {
               name = n;
               val.set(p);
            }
         }

         private static int nextRowId = 1;

         private static RowItem nextItem() {
            return new RowItem("Row" + String.valueOf(++nextRowId), nextRowId * 10.0);
         }

         @Override
         public void start(final Stage primaryStage) throws Exception {
            final TableView<RowItem> tableView = new TableView<RowItem>();

            final Button updateButton = new Button("Set TableView.refresh Property");
            updateButton.setOnAction(new EventHandler<ActionEvent>() {
               @Override
               public void handle(final ActionEvent actionEvent) {
                  tableView.getProperties().put("TableView.refresh", "true");
               }
            });

            final TableColumn<RowItem, String> nameCol = new TableColumn<RowItem, String>("Item");
            final TableColumn<RowItem, Number> valueCol = new TableColumn<RowItem, Number>("Value");

            nameCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<RowItem, String>, ObservableValue<String>>() {
               @Override
               public ObservableValue<String> call(final CellDataFeatures<RowItem, String> cellData) {
                  return new ReadOnlyStringWrapper(cellData.getValue().name);
               }
            });

            valueCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<RowItem, Number>, ObservableValue<Number>>() {
               @Override
               public ObservableValue<Number> call(final CellDataFeatures<RowItem, Number> cellData) {
                  return cellData.getValue().val;
               };
            });

            tableView.getColumns().addAll(nameCol, valueCol);
            tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
            for (int i = 0; i < 100; i++) {
               tableView.getItems().add(nextItem());
            }

            final VBox tb = VBoxBuilder.create().children(tableView).build();
            final VBox vb = VBoxBuilder.create().children(tb, updateButton).build();
            VBox.setVgrow(tb, Priority.ALWAYS);
            VBox.setVgrow(tableView, Priority.ALWAYS);
            
            primaryStage.setScene(new Scene(vb));
            primaryStage.setHeight(500.0);
            primaryStage.show();
            
            VirtualFlow flow = (VirtualFlow) tableView.lookup("VirtualFlow");
            Region clip = (Region) flow.getChildrenUnmodifiable().get(0);
            System.out.println("ClippedContainer: " + clip.getClass().getName());
            final Group clipNode = (Group) clip.getChildrenUnmodifiable().get(0);
            final Group sheet = (Group) clipNode.getChildrenUnmodifiable().get(0);
            
            System.out.println("Sheet child count: " + sheet.getChildren().size());

            sheet.getChildren().addListener(new InvalidationListener() {
               
               @Override
               public void invalidated(Observable arg0) {
                  System.out.println("Sheet child count: " + sheet.getChildren().size());
               }
            });
            
            Timeline heightAnim = TimelineBuilder.create()
                  .cycleCount(50)
                  .keyFrames(
                        new KeyFrame(Duration.seconds(0.0), new KeyValue(tableView.maxHeightProperty(), 0.0)),
                        new KeyFrame(Duration.seconds(0.5), new KeyValue(tableView.maxHeightProperty(), 0.0)),
                        new KeyFrame(Duration.seconds(1.0), new KeyValue(tableView.maxHeightProperty(), 800.0)),
                        new KeyFrame(Duration.seconds(1.5), new KeyValue(tableView.maxHeightProperty(), 800.0))
                        )
                        
                  .build();
            
            heightAnim.play();
         }

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

        Attachments

          Activity

            People

            • Assignee:
              jgiles Jonathan Giles
              Reporter:
              aswanepoejfx Abraham Swanepoel (Inactive)
            • Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Due:
                Created:
                Updated:
                Resolved:
                Imported: