-
Bug
-
Resolution: Fixed
-
P4
-
8
-
OS X 10.9 Java 1.8.0ea b117
If I use the system MenuBar with OS X the related stages never seen to be GC'd. If I decide not to use the system MenuBar then the whole stage collects as expected.
Run the provided test class and open 2 windows with each of the top buttons (4 total). Close all the new stages except the primary one. Then click on the "Print References" button and watch the console.
The console will show that only 2 of the 4 stages were GC'd. As shown in the test case I have tried "turning off" the system MenuBar when the stage is no longer needed and this doesn't have any effect on the memory leak.
********************************** Test Class ********************************************
import java.lang.ref.WeakReference;
import java.util.HashSet;
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 {
@Override public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(350);
primaryStage.setWidth(500);
final Set<WeakReference<Object>> refs = new HashSet<>();
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()));
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();
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);
pane.setTop(menuBar);
stage.setScene(new Scene(pane, 800, 600));
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
public void handle(WindowEvent event) {
// this line doesn't make any difference to the memory leak...
menuBar.setUseSystemMenuBar(false);
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);
}
}
Run the provided test class and open 2 windows with each of the top buttons (4 total). Close all the new stages except the primary one. Then click on the "Print References" button and watch the console.
The console will show that only 2 of the 4 stages were GC'd. As shown in the test case I have tried "turning off" the system MenuBar when the stage is no longer needed and this doesn't have any effect on the memory leak.
********************************** Test Class ********************************************
import java.lang.ref.WeakReference;
import java.util.HashSet;
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 {
@Override public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(350);
primaryStage.setWidth(500);
final Set<WeakReference<Object>> refs = new HashSet<>();
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()));
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();
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);
pane.setTop(menuBar);
stage.setScene(new Scene(pane, 800, 600));
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
public void handle(WindowEvent event) {
// this line doesn't make any difference to the memory leak...
menuBar.setUseSystemMenuBar(false);
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);
}
}