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

Memory Leak in ControlAcceleratorSupport

XMLWordPrintable

        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

              arapte Ambarish Rapte
              shadzic Samir Hadzic
              Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

                Created:
                Updated:
                Resolved: