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

Memory Leak in ControlAcceleratorSupport

    Details

      Description

      When you repeatedly add a MenuItem in a Menu, a memory leak happens that can lead to severe performance issues.

       Step to reproduce:
      Run this sample :
      "import javafx.application.Application;
      import javafx.event.ActionEvent;
      import javafx.event.EventHandler;
      import javafx.scene.Scene;
      import javafx.scene.control.*;
      import javafx.scene.layout.BorderPane;
      import javafx.stage.Stage;


      public class HelloFX extends Application {

          @Override
          public void start(Stage stage) {
              BorderPane pane = new BorderPane();
              MenuBar menuBar = new MenuBar();

              Menu menu = new Menu("TEST");
              MenuItem item = new MenuItem("toto");

              menu.getItems().add(item);
              menuBar.getMenus().add(menu);
              pane.setTop(menuBar);

              Button button = new Button("LEAK TEST");
              button.setOnAction(new EventHandler<ActionEvent>() {
                  @Override
                  public void handle(ActionEvent actionEvent) {
                      for(int i =0;i<10;++i){
                          menu.getItems().clear();
                          menu.getItems().add(item);
                      }
                  }
              });
              pane.setCenter(button);
              stage.setTitle("Hello World");
              stage.setScene(new Scene(pane, 300, 275));
              stage.show();
          }

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

      }
      "
      - Run the Sample and take a profiler
      - Click on the button "LEAK TEST"
      - Take a heap dump and count the number of "ControlAcceleratorSupport$$Lambda"
      - Click again several times on the button
      - Take a new heap dump and you will see that you have a bunch more ControlAcceleratorSupport$$Lambda

      Explanation:

      Each time we add a MenuItem inside the Menu, this line inside the Items ListChangeListener is called

       ControlAcceleratorSupport.doAcceleratorInstall(c.getAddedSubList(), scene);

      And inside this method, we do :
       // We also listen to the accelerator property for changes, such
                      // that we can update the scene when a menu item accelerator changes.
                      menuitem.acceleratorProperty().addListener((observable, oldValue, newValue) -> {
                          final Map<KeyCombination, Runnable> accelerators = scene.getAccelerators();

                          // remove the old KeyCombination from the accelerators map
                          Runnable _acceleratorRunnable = accelerators.remove(oldValue);

                          // and put in the new accelerator KeyCombination, if it is not null
                          if (newValue != null) {
                              accelerators.put(newValue, _acceleratorRunnable);
                          }
                      });

      Therefore we are adding a Listener to the acceleratorProperty() of this menuItem and we are never removing it. (Check removeAcceleratorsFromScene(c.getRemoved(), scene); that is called when the clear() is called on the Menu).

      Thus we are pilling up a bunch of Listeners on the MenuItem.

      This issue is present since JDK 8 and reproduced in JDK 11 build 23

        Attachments

          Activity

            People

            • Assignee:
              aghaisas Ajit Ghaisas
              Reporter:
              shadzic Samir Hadzic
            • Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated: