JDK-8208088 : Memory Leak in ControlAcceleratorSupport
  • Type: Bug
  • Component: javafx
  • Sub-Component: controls
  • Affected Version: 8u20,openjfx11
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2018-07-23
  • Updated: 2021-11-23
  • Resolved: 2021-04-16
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 Other
8u321Fixed openjfx17Fixed
Related Reports
Relates :  
Relates :  
Relates :  
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
Comments
Changeset: 05ab7992 Author: Ambarish Rapte <arapte@openjdk.org> Date: 2021-04-16 05:02:22 +0000 URL: https://git.openjdk.java.net/jfx/commit/05ab7992
16-04-2021

Targeting for openjfx12. If a safe fix can be found before the RDP2 deadline, we might consider it for 11.
23-07-2018

I can confirm the leak. The code in question was added in JDK 8u20 as part of the fix for JDK-8094409 (aka RT-28136). The leak is fairly small, but still should be fixed.
23-07-2018