-
Bug
-
Resolution: Fixed
-
P3
-
8
-
OS X 10.9 and 10.8 Java 1.8.0ea b117
This bug is closely related to bug RT-34634 but I wanted to write it up separately since I think it might define an additional memory leak. While RT-34634 is about the Stages not being GC'd, this bug is about the MenuBars also not being GC'd.
As I mentioned in a comment toRT-34634 I believe the Stage leak might be caused by the static WeakHashMap<Stage, MenuBarSkin> in MenuBarSkin. In the test code below I have added a line that allows the Stages to be collected, namely removing the MenuBars from the scene when the window is closing so that the skin no longer has a reference back to its Stage.
But I have also added the MenuBar instances to my set of WeakReferences so you can see after creating and closing a Stage with the second button, now the MenuBar's are never GC'd. Run the following test class and create then close Stages with the second button. Then use the Print References buttons to observe that the MenuBar instances are never collected.
********************************* Test Class ***************************************
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.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class StageTest 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);
Button showButton = new Button("New View (will collect)...");
showButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Stage newStage = createNewStage(false);
newStage.show();
refs_.add( new WeakReference<Object>(newStage) );
}
});
Button showButton2 = new Button("New View (won't collect)...");
showButton2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Stage newStage = createNewStage(true);
newStage.show();
refs_.add( new WeakReference<Object>(newStage) );
}
});
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("*********************************************");
}
});
VBox box = new VBox(20, new HBox(10, showButton, showButton2), printButton );
box.setMaxHeight(Region.USE_PREF_SIZE);
box.setMaxWidth(Region.USE_PREF_SIZE);
box.setAlignment(Pos.TOP_CENTER);
primaryStage.setScene(new Scene( new StackPane(box) ));
primaryStage.show();
}
private Stage createNewStage(boolean systemMenubar) {
final Stage stage = new Stage();
final BorderPane pane = new BorderPane();
final MenuBar menuBar = new MenuBar();
menuBar.setUseSystemMenuBar(systemMenubar);
Menu menu = new Menu("File");
menu.getItems().add(new MenuItem("Open..."));
menuBar.getMenus().add(menu);
refs_.add(new WeakReference<Object>(menuBar));
pane.setTop(menuBar);
stage.setScene(new Scene(pane, 800, 600));
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
public void handle(WindowEvent event) {
// this line makes no difference on the leak
menuBar.setUseSystemMenuBar(false);
// this line will allow the stage to be collected as I commented in
// related bugRT-34634
pane.getChildren().remove(menuBar);
event.consume();
// only collects if I "later" the hide (bug RT-34631)
Platform.runLater(new Runnable() {
public void run() {
stage.hide();
}
});
}
});
return stage;
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
As I mentioned in a comment to
But I have also added the MenuBar instances to my set of WeakReferences so you can see after creating and closing a Stage with the second button, now the MenuBar's are never GC'd. Run the following test class and create then close Stages with the second button. Then use the Print References buttons to observe that the MenuBar instances are never collected.
********************************* Test Class ***************************************
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.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class StageTest 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);
Button showButton = new Button("New View (will collect)...");
showButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Stage newStage = createNewStage(false);
newStage.show();
refs_.add( new WeakReference<Object>(newStage) );
}
});
Button showButton2 = new Button("New View (won't collect)...");
showButton2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
Stage newStage = createNewStage(true);
newStage.show();
refs_.add( new WeakReference<Object>(newStage) );
}
});
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("*********************************************");
}
});
VBox box = new VBox(20, new HBox(10, showButton, showButton2), printButton );
box.setMaxHeight(Region.USE_PREF_SIZE);
box.setMaxWidth(Region.USE_PREF_SIZE);
box.setAlignment(Pos.TOP_CENTER);
primaryStage.setScene(new Scene( new StackPane(box) ));
primaryStage.show();
}
private Stage createNewStage(boolean systemMenubar) {
final Stage stage = new Stage();
final BorderPane pane = new BorderPane();
final MenuBar menuBar = new MenuBar();
menuBar.setUseSystemMenuBar(systemMenubar);
Menu menu = new Menu("File");
menu.getItems().add(new MenuItem("Open..."));
menuBar.getMenus().add(menu);
refs_.add(new WeakReference<Object>(menuBar));
pane.setTop(menuBar);
stage.setScene(new Scene(pane, 800, 600));
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
public void handle(WindowEvent event) {
// this line makes no difference on the leak
menuBar.setUseSystemMenuBar(false);
// this line will allow the stage to be collected as I commented in
// related bug
pane.getChildren().remove(menuBar);
event.consume();
// only collects if I "later" the hide (bug RT-34631)
Platform.runLater(new Runnable() {
public void run() {
stage.hide();
}
});
}
});
return stage;
}
public static void main(String[] args) throws Exception {
launch(args);
}
}