-
Bug
-
Resolution: Fixed
-
P4
-
8u20
-
Linux Java 8u20 b05
If you use mnemonics with a Button it doesn't remove its mnemonic when it is removed from its scene. This causes the button and anything with a reference to it to not be GC'd.
Run the following test class to observe the memory leak:
1. Press repeatedly on the "Replace Button Pane" button and observe that it swaps a new pane into the center of the stage with a newly constructed button that contains a mnemonic.
2. Press the "Print References" button and look at your console. It will show that after a GC has been run there is still a button pane HBox in memory for each time you pressed the button.
3. Close the stage and run the test class again. This time turn on the "Enable Workaround" checkbox before pressing the "Replace Button Pane" button repeatedly.
4. Press the "Print References" button for this second run and you will see that the GC collects all but the currently visible button pane HBox.
You will note in the test case that the workaround simply removes the mnemonic old buttons from the scene manually and this fixes the memory leak. I noticed that in LabeledSkinBase it deals with a parent changing for removing its old mnemonic from its scene but it does not deal with the scene itself changing.
***************************** Test Classs ********************************************
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.input.Mnemonic;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import com.sun.javafx.scene.control.behavior.TextBinding;
public class MnemonicTest extends Application {
final Set<WeakReference<Object>> refs_ = new HashSet<>();
@Override public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(350);
primaryStage.setWidth(500);
final CheckBox checkbox = new CheckBox("Enable Workaround");
checkbox.setSelected(false);
final StackPane buttonPaneHolder = new StackPane();
Button replaceButton = new Button("Replace Button Pane");
replaceButton.setOnAction(new EventHandler<ActionEvent>() {
int count = 0;
public void handle(ActionEvent event) {
count++;
if ( checkbox.isSelected() ) {
Node node = buttonPaneHolder.getChildren().get(0);
if ( node != null ) {
Button oldButton =
(Button)((Region)node).getChildrenUnmodifiable().get(1);
oldButton.getScene().removeMnemonic(new Mnemonic(oldButton,
new TextBinding(
oldButton.getText()).getMnemonicKeyCombination()));
}
}
Button button = new Button("_Test Button");
HBox buttonPane = new HBox(20, new Label("Button Pane #" +count),
button);
buttonPane.setAlignment(Pos.BASELINE_CENTER);
buttonPane.setMaxHeight( Region.USE_PREF_SIZE );
buttonPaneHolder.getChildren().setAll( buttonPane );
refs_.add( new WeakReference<Object>(buttonPane) );
}
});
replaceButton.fire(); // initialize button pane
Button printButton = new Button("Print References");
printButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Runtime.getRuntime().gc();
Set<WeakReference<Object>> collected = new HashSet<>();
for ( WeakReference<Object> ref: refs_ ) {
if ( ref.get() == null ) {
collected.add(ref);
}
}
refs_.removeAll(collected);
System.out.println("*********************************************");
System.out.println(String.format("%d objects collected.",
collected.size()));
System.out.println(String.format("%d objects remain.",
refs_.size()));
HashMap<String, Integer> remainingMap = new HashMap<>();
for ( WeakReference<Object> ref: refs_ ) {
Object ob = ref.get();
if ( ob != null ) {
String className = ob.getClass().getSimpleName();
Integer count = remainingMap.get(className);
count = count == null ? 1 : count + 1;
remainingMap.put(className, count);
}
}
for ( Entry<String, Integer> entry: remainingMap.entrySet() ) {
System.out.println(
" " + entry.getValue() + " " +entry.getKey() +" remain");
}
System.out.println("*********************************************");
}
});
HBox box = new HBox(10, checkbox, replaceButton, printButton );
box.setMaxWidth(Region.USE_PREF_SIZE);
box.setAlignment(Pos.BASELINE_LEFT);
box.setPadding( new Insets(10));
BorderPane borderPane = new BorderPane(buttonPaneHolder, null, null, box, null);
BorderPane.setAlignment(box, Pos.CENTER);
primaryStage.setScene( new Scene( borderPane ));
primaryStage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
Run the following test class to observe the memory leak:
1. Press repeatedly on the "Replace Button Pane" button and observe that it swaps a new pane into the center of the stage with a newly constructed button that contains a mnemonic.
2. Press the "Print References" button and look at your console. It will show that after a GC has been run there is still a button pane HBox in memory for each time you pressed the button.
3. Close the stage and run the test class again. This time turn on the "Enable Workaround" checkbox before pressing the "Replace Button Pane" button repeatedly.
4. Press the "Print References" button for this second run and you will see that the GC collects all but the currently visible button pane HBox.
You will note in the test case that the workaround simply removes the mnemonic old buttons from the scene manually and this fixes the memory leak. I noticed that in LabeledSkinBase it deals with a parent changing for removing its old mnemonic from its scene but it does not deal with the scene itself changing.
***************************** Test Classs ********************************************
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.input.Mnemonic;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import com.sun.javafx.scene.control.behavior.TextBinding;
public class MnemonicTest extends Application {
final Set<WeakReference<Object>> refs_ = new HashSet<>();
@Override public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(350);
primaryStage.setWidth(500);
final CheckBox checkbox = new CheckBox("Enable Workaround");
checkbox.setSelected(false);
final StackPane buttonPaneHolder = new StackPane();
Button replaceButton = new Button("Replace Button Pane");
replaceButton.setOnAction(new EventHandler<ActionEvent>() {
int count = 0;
public void handle(ActionEvent event) {
count++;
if ( checkbox.isSelected() ) {
Node node = buttonPaneHolder.getChildren().get(0);
if ( node != null ) {
Button oldButton =
(Button)((Region)node).getChildrenUnmodifiable().get(1);
oldButton.getScene().removeMnemonic(new Mnemonic(oldButton,
new TextBinding(
oldButton.getText()).getMnemonicKeyCombination()));
}
}
Button button = new Button("_Test Button");
HBox buttonPane = new HBox(20, new Label("Button Pane #" +count),
button);
buttonPane.setAlignment(Pos.BASELINE_CENTER);
buttonPane.setMaxHeight( Region.USE_PREF_SIZE );
buttonPaneHolder.getChildren().setAll( buttonPane );
refs_.add( new WeakReference<Object>(buttonPane) );
}
});
replaceButton.fire(); // initialize button pane
Button printButton = new Button("Print References");
printButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Runtime.getRuntime().gc();
Set<WeakReference<Object>> collected = new HashSet<>();
for ( WeakReference<Object> ref: refs_ ) {
if ( ref.get() == null ) {
collected.add(ref);
}
}
refs_.removeAll(collected);
System.out.println("*********************************************");
System.out.println(String.format("%d objects collected.",
collected.size()));
System.out.println(String.format("%d objects remain.",
refs_.size()));
HashMap<String, Integer> remainingMap = new HashMap<>();
for ( WeakReference<Object> ref: refs_ ) {
Object ob = ref.get();
if ( ob != null ) {
String className = ob.getClass().getSimpleName();
Integer count = remainingMap.get(className);
count = count == null ? 1 : count + 1;
remainingMap.put(className, count);
}
}
for ( Entry<String, Integer> entry: remainingMap.entrySet() ) {
System.out.println(
" " + entry.getValue() + " " +entry.getKey() +" remain");
}
System.out.println("*********************************************");
}
});
HBox box = new HBox(10, checkbox, replaceButton, printButton );
box.setMaxWidth(Region.USE_PREF_SIZE);
box.setAlignment(Pos.BASELINE_LEFT);
box.setPadding( new Insets(10));
BorderPane borderPane = new BorderPane(buttonPaneHolder, null, null, box, null);
BorderPane.setAlignment(box, Pos.CENTER);
primaryStage.setScene( new Scene( borderPane ));
primaryStage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
- duplicates
-
JDK-8095482 [Mnemonic] Memory leak with Mnemonic Parsing when Button inside Pane
- Closed