The MenuButtonSkinBase adds a scene listener to add and remove change listeners to the acceleratorProperty, however it also calls ControlAcceleratorSupport which does the same. Each time the MenuButton's scene changes, multiple listeners are added, but only 1 copy is removed causing the number of listeners to grow continuously.
This test demonstrates the issue:
@Test
public void testMemoryButtonSkinDoesntAddAdditionalListeners() {
MenuItem menuItem = new MenuItem("Menu Item");
MenuButton menuButton = new MenuButton("Menu Button", null, menuItem);
StackPane root = new StackPane(menuButton);
StageLoader sl = new StageLoader(root);
assertEquals(1, getListenerCount(menuItem.acceleratorProperty()));
root.getChildren().remove(menuButton);
assertEquals(0, getListenerCount(menuItem.acceleratorProperty()));
root.getChildren().add(menuButton);
assertEquals(1, getListenerCount(menuItem.acceleratorProperty()));
sl.dispose();
}
Alternatively run the following app, click the button to add/remove the menu a few times, then profile and check the number of listeners created by ControlAcceleratorSupport.java, then click the button some more and check the number again. It keeps growing.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MenuButtonChangeListenerTest extends Application {
@Override
public void start(Stage primaryStage) {
VBox root = new VBox();
Scene scene = new Scene(root, 800, 600);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
MenuItem menuItem = new MenuItem("Menu Item");
Menu mainMenu = new Menu("Main Menu", null, menuItem);
MenuButton menuButton = new MenuButton("Main Menu", null, mainMenu);
Button button = new Button("Add/Remove Menu");
button.setOnAction((e)->{
if (root.getChildren().contains(menuButton)) {
root.getChildren().remove(menuButton);
}
else {
root.getChildren().add(menuButton);
}
});
root.getChildren().add(button);
}
}
This test demonstrates the issue:
@Test
public void testMemoryButtonSkinDoesntAddAdditionalListeners() {
MenuItem menuItem = new MenuItem("Menu Item");
MenuButton menuButton = new MenuButton("Menu Button", null, menuItem);
StackPane root = new StackPane(menuButton);
StageLoader sl = new StageLoader(root);
assertEquals(1, getListenerCount(menuItem.acceleratorProperty()));
root.getChildren().remove(menuButton);
assertEquals(0, getListenerCount(menuItem.acceleratorProperty()));
root.getChildren().add(menuButton);
assertEquals(1, getListenerCount(menuItem.acceleratorProperty()));
sl.dispose();
}
Alternatively run the following app, click the button to add/remove the menu a few times, then profile and check the number of listeners created by ControlAcceleratorSupport.java, then click the button some more and check the number again. It keeps growing.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MenuButtonChangeListenerTest extends Application {
@Override
public void start(Stage primaryStage) {
VBox root = new VBox();
Scene scene = new Scene(root, 800, 600);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
MenuItem menuItem = new MenuItem("Menu Item");
Menu mainMenu = new Menu("Main Menu", null, menuItem);
MenuButton menuButton = new MenuButton("Main Menu", null, mainMenu);
Button button = new Button("Add/Remove Menu");
button.setOnAction((e)->{
if (root.getChildren().contains(menuButton)) {
root.getChildren().remove(menuButton);
}
else {
root.getChildren().add(menuButton);
}
});
root.getChildren().add(button);
}
}
- relates to
-
JDK-8295426 MenuButtonSkin: memory leak when changing skin
-
- Resolved
-