JDK-8087942 : Mac: Window memory leak if default close handling is used for dealing with OS close request.
  • Type: Bug
  • Component: javafx
  • Sub-Component: window-toolkit
  • Affected Version: 8
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2013-12-02
  • Updated: 2018-09-05
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
In OS X, Stages that are closed with the window decoration are never GC'd if the default close handling is used. I was only able to get simple stages to be collected if I handled the closing of the stage AND I used a runLater to perform the close in the WindowEvent handler.

Run the provided test class and show 2 windows with the left button and 2 windows with the right button. Close all the windows except the primary one and then click on the "Print References" button and watch the console.

The console will show that only 2 of the 4 created stages were GC'd.

***************************** 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.layout.HBox;
import javafx.scene.layout.Pane;
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 defaultClose) { 
      final Stage stage = new Stage(); 
       
      stage.setScene(new Scene(new Pane(), 800, 600)); 
      if ( !defaultClose ) {
         stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
            public void handle(WindowEvent event) {
               event.consume();
               // only collects if I "later" the hide
               Platform.runLater(new Runnable() {
                  public void run() {
                     stage.hide();
                  }
               });
            }
         });
      }
       
      return stage; 
   } 
    
   public static void main(String[] args) throws Exception { 
      launch(args); 
   } 

}
Comments
Anthony: I just confirmed that the memory leak also exists in 10.8. Pretty odd that putting the window.hide() in a runLater allows the window to be collected. That seems like its more than just forgetting to remove the window from a list...
03-12-2013

Charles: is this issue specific to OS X 10.9, or you can reproduce it on 10.8- as well? It's unlikely to be directly related to RT-34554. Looks like some sort of a memory leak. Perhaps the default method that handles the closing request in Quantum doesn't remove the closed window from a list, hence retaining a reference. Will investigate for 9, and perhaps back port the fix to 8u20.
03-12-2013

I think this might be a duplicate of RT-34554.
02-12-2013