JDK-8222212 : Memory Leak with SwingNode using Drag and Drop function
  • Type: Bug
  • Component: javafx
  • Sub-Component: swing
  • Affected Version: 8u172,openjfx11
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2019-03-22
  • Updated: 2022-07-07
  • Resolved: 2019-06-26
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.
JDK 8 Other
8-poolUnresolved openjfx13Fixed
Related Reports
Relates :  
Description
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).


Comments
Changeset: 42c50cfa7dbf Author: psadhukhan Date: 2019-06-26 11:07 +0530 URL: http://hg.openjdk.java.net/openjfx/jfx-dev/rt/rev/42c50cfa7dbf
26-06-2019

Looks good, except that you introduced a white-space error that will cause jcheck to fail: tests/system/src/test/java/test/javafx/embed/swing/SwingNodeDnDMemoryLeakTest.java:63: Tab character Please fix that (and run 'hg jcheck' to verify) before pushing. This is a straight-forward fix so no need for a second review. +1
25-06-2019

Thanks Kevin for review. Modified test in the webrev http://cr.openjdk.java.net/~psadhukhan/fx/8222212/webrev.1/
25-06-2019

Looks good with two comments on the test: 1. To align with current best practices of a minimum of 10 seconds for startup, as well as simplifying the startup code, the try/catch block on lines 67-76 can be replaced with: assertTrue("Timeout waiting for Application to launch", launchLatch.await(10, TimeUnit.SECONDS)); You will need to add "throws Exception" to the method declaration so this will compile. 2. You can remove some unused imports
24-06-2019

Request to review: http://cr.openjdk.java.net/~psadhukhan/8222212/webrev.0/ When DnD is initialised via SwingNodeContent#initDnD(), a strong reference to SwingNode is kept by FXDnD class via FXDnDInteropN class, so SwingNode is not disposed. Proposed fix is to WeakReference the swingNode object when it is stored in FXDnDInteropN . Manual DnD tests were run and no regression was seen.
21-06-2019

It seems from jvisualvm via "Show Nearest GC Root" that all the instances of SwingNode has been referenced from Disposer object through SwingNodeDisposer (attached strongref screenshot)
20-06-2019

I tried to profile the application by repeatedly doing "Open Stage" "Close Stage" I can see in jvisualvm profiler that com.sun.javafx.embed.swing.Disposer.records.count is going on increasing. Attached is one of the heapdump. If this is the problem (ie count is never getting decreased) then I can see it is getting increased even without having DropTarget ie even after commenting setDropTarget() in the JavaFxMemoryLeak testcase. JLabel label = new JLabel("SWING"); // label.setDropTarget(new DropTarget()); If we call setDropTarget(), SwingNodeInteropN.SwingNodeContent#initDnD() gets called which creates a reference of swingNode inside FXDnD [this.dnd = new FXDnD(swingNode)] but if we do not call setDropTarget(), then I do not see initDnD() getting called so SwingNode reference is not created but even then com.sun.javafx.embed.swing.Disposer.records.count goes on increasing, which is strange. So maybe not related to DnD having reference to SwingNode. Clicking on "Perform GC" in profiler does not make any difference.
20-06-2019

This likely also affects openjfx11 and later, but needs to be tested.
09-04-2019