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

Memory leak in TableView after interacting with TableMenuButton

XMLWordPrintable

    • b19
    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      OS: Ubuntu Cinnamon 23.10 x86_64
      Kernel: 6.5.0-44-generic
      CPU: AMD Ryzen 9 5950X
      GPU: AMD 7900 XTX
      RAM: 64GB
      Java: 21.0.4 and Oracle OpenJDK 23 (I tested both)


      A DESCRIPTION OF THE PROBLEM :
      In JavaFX, a memory leak occurs in the TableView when the TableMenuButton is used to change column visibility. After interacting with the TableMenuButton, each modification of the table's column configuration (setting columns using setAll()) results in a gradual increase in heap memory usage. The memory is not sufficiently reclaimed by garbage collection, leading to significant memory consumption over time.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Create a JavaFX application with a TableView that includes at least one column.
      2. Enable the TableMenuButton by calling tableView.setTableMenuButtonVisible(true).
      3. Add a button to the UI that, when clicked, repeatedly calls tableView.getColumns().setAll() to reset the column configuration.
      4. (Optional) Place step 3 in loop with thousand of iterations or add hundreds columns to tableView to demonstrate the memory leak quickly.
      5. Interact with the TableMenuButton by clicking it (no need to change column visibility).
      6. After clicking the TableMenuButton, repeatedly click the button that resets the columns.
      7. Observe a gradual increase in memory usage with each reset. Garbage collection reduces memory usage slightly, but not enough to prevent a memory leak.
      8. Repeat the reset operation several times to see significant heap memory growth.


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Memory usage should remain stable after resetting the columns in the TableView, even after interacting with the TableMenuButton. Garbage collection should fully release any unused memory, preventing memory leaks when repeatedly setting column visibility and order.
      ACTUAL -
      After interacting with the TableMenuButton, each subsequent column reset operation results in increased memory usage. Garbage collection reclaims only a small portion of memory, leading to a memory leak. Repeated resets cause the application to consume several gigabytes of RAM over time, especially with more columns.

      ---------- BEGIN SOURCE ----------
      import javafx.application.Application;
      import javafx.collections.FXCollections;
      import javafx.collections.ObservableList;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.TableColumn;
      import javafx.scene.control.TableView;
      import javafx.scene.layout.VBox;
      import javafx.stage.Stage;

      public class HelloApplication extends Application {
          // Steps to reproduce:
          // 1. Run the application.
          // 2. Click the table menu button (located in the upper right corner of the TableView).
          // 3. No need to change the visibility or order of any column—just open the menu.
          // 4. Click "Set columns" button
          // 5. Observe the increased memory usage after each click.
          // Even performing garbage collection (GC) typically reduces heap usage by only about 10%.
          // 6. (Optional) Repeat step 4 multiple times. Notice that each click further increases heap memory usage.

          // Important Notes:
          // If you do not interact with the table menu button and only click the "Set columns" button, memory usage remains stable.
          //
          // The issue is exacerbated with tables that contain more columns,
          // where users change column visibility and order, then save and frequently restore these configurations.
          //
          // Attempts to mitigate the issue using tableView.getColumns().clear() before applying setAll(tableColumns)
          // do not resolve the memory leak.
          //
          // Impact: This problem becomes particularly noticeable in applications where
          // users dynamically customize column visibility and order. Over time, repeated interactions with the table menu button,
          // followed by saving/restoring column configurations, result in significant memory consumption.

          @Override
          public void start(Stage stage) {
              VBox vBox = new VBox();

              Scene scene = new Scene(vBox, 320, 240);
              stage.setScene(scene);
              stage.show();

              // Create a TableView with one column and set table menu button visible.
              TableView<String> tableView = new TableView<>();
              tableView.getColumns().add(new TableColumn<>("Column"));
              // Enable the table menu button, which triggers the memory issue.
              tableView.setTableMenuButtonVisible(true);

              // Save the original list of columns for later restoration.
              ObservableList<TableColumn<String, ?>> tableColumns = FXCollections.observableArrayList(tableView.getColumns());

              // Add a button to simulate setting columns repeatedly.
              Button button = new Button("Set columns");
              button.setOnMouseClicked(event -> {
                  // This loop is to demonstrate the memory leak quickly.
                  // On larger tables with more columns, even a lower iteration count (e.g., 200)
                  // can cause significant memory usage, potentially over 16GB of heap use.
                  for (int i = 0; i < 10000; i++) {
                      tableView.getColumns().setAll(tableColumns);
                  }

                  // (Optional) Print out memory usage after iterations for tracking purposes.
                  System.gc();
                  Runtime runtime = Runtime.getRuntime();
                  long usedMemory = runtime.totalMemory() - runtime.freeMemory();
                  System.out.println("Memory used (in MB): " + usedMemory / (1024 * 1024));
              });

              // Add the table view and button to the UI layout.
              vBox.getChildren().addAll(tableView, button);
          }

          public static void main(String[] args) {
              launch();
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Currently, there is no effective workaround. Attempts to mitigate the issue by calling tableView.getColumns().clear() before resetting the columns do not resolve the memory leak. The issue persists after interacting with the TableMenuButton.

      FREQUENCY : always

      WORKAROUND: exists, see comments.

        1. Screenshot 2024-10-10 at 15.29.18.png
          35 kB
          Andy Goryachev
        2. Test.java
          3 kB
          Anupam Dev
        3. visual vm graph.png
          40 kB
          Anupam Dev

            mhanl Marius Hanl
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            9 Start watching this issue

              Created:
              Updated:
              Resolved: