ADDITIONAL SYSTEM INFORMATION :
Windows 10 / Java 8u172, 8u201, 8u202
A DESCRIPTION OF THE PROBLEM :
The com.sun.javafx.embed.swing.Disposer holds onto the SwingNode by way of a strong reference through the SwingNodeDisposer: SwingNodeDisposer -> lwframe -> content -> dnd -> SwingNode
Functionally, you've got strong references dangling off of the Disposer in a loop that will not collect.
It looks like JDK-8189280 may have introduced this, it added framework evident in this leak and I was unable to reproduce the leak on Java 8u131
The example I have is taken from that bug report, modified only to enable the display of the stage and to set a drop target on the swing component.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a SwingNode, place content that has set a Drop Target. Put the content on the screen, take it off the screen, and then free all references to the node and associated content.
Invoke the Garbage Collector, and inspect the contents of com.sun.javafx.embed.swing.Disposer.records via a debugger or profiling tool.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The SwingNode will be garbage collected.
ACTUAL -
The SwingNode does not garbage collect because it is not eligible for collection.
---------- BEGIN SOURCE ----------
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javafxmemoryleak;
import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.awt.dnd.DropTarget;
import javax.swing.JLabel;
public class JavaFxMemoryLeak extends Application {
private Stage tempStage;
@Override
public void start(final Stage primaryStage) {
Button openDialog = new Button();
openDialog.setText("Open Dialog");
openDialog.setOnAction(e -> openDialog(primaryStage));
Button openStage = new Button();
openStage.setText("Open Stage");
openStage.setOnAction(e -> openStage(primaryStage));
Button closeStage = new Button();
closeStage.setText("Close Stage");
closeStage.setOnAction(e -> closeStage());
HBox root = new HBox();
root.getChildren().add(openDialog);
root.getChildren().add(openStage);
root.getChildren().add(closeStage);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Memory Test");
primaryStage.setScene(scene);
primaryStage.show();
}
private void closeStage() {
if (tempStage != null)
tempStage.close();
tempStage = null;
}
private void openDialog(Stage primaryStage) {
StackPane root = new StackPane();
ProgressIndicator pi = new ProgressIndicator();
pi.parentProperty().addListener((obs, oldVal, newVal) -> System.out.println("Parent: " + newVal));
pi.sceneProperty().addListener((obs, oldVal, newVal) -> {
System.out.println("Dialog Scene: " + newVal);
});
root.getChildren().add(pi);
Alert dialog = new Alert(Alert.AlertType.INFORMATION);
dialog.getDialogPane().setContent(root);
dialog.initOwner(primaryStage);
dialog.showAndWait();
}
private void openStage(Stage primaryStage) {
closeStage();
BorderPane root = new BorderPane();
SwingNode sw = new SwingNode();
JLabel label = new JLabel("SWING");
label.setDropTarget(new DropTarget());
sw.setContent(label);
root.centerProperty().set(sw);
// ProgressIndicator pi = new ProgressIndicator();
// pi.parentProperty().addListener((obs, oldVal, newVal) -> System.out.println("Parent: " + newVal));
// pi.sceneProperty().addListener((obs, oldVal, newVal) -> {
// System.out.println("Scene: " + newVal);
// });
// root.centerProperty().set(pi);
final Stage stage = new Stage();
Button btn = new Button();
btn.setText("Stage Close");
btn.setOnAction(e -> {
stage.close();
});
root.bottomProperty().set(btn);
Scene scene = new Scene(root, 150, 100);
stage.setScene(scene);
tempStage = stage;
//If we don't show the stage, resources are never released
stage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
System.out.println("java.version = " + System.getProperty("java.version"));
System.out.println("java.runtime.version = " + System.getProperty("java.runtime.version"));
launch(args);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Don't use Drag and Drop with Swing Nodes.
I've not yet seen a more useful workaround (still looking).