-
Bug
-
Resolution: Fixed
-
P3
-
jfx11, 8u20
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8267647 | 8u321 | Kumar Abhishek | P3 | Resolved | Fixed | b01 |
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
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
- backported by
-
JDK-8267647 Memory Leak in ControlAcceleratorSupport
- Resolved
- relates to
-
JDK-8274022 Additional Memory Leak in ControlAcceleratorSupport
- Resolved
-
JDK-8274023 Memory Leak in ControlAcceleratorSupport (JFX 17)
- Closed
-
JDK-8276917 Memory Leak caused by MenuBarButton in ControlAcceleratorSupport
- Closed
(1 links to)