JDK-8262518 : SwingNode.setContent does not close previous content, resulting in memory leak
  • Type: Bug
  • Component: javafx
  • Sub-Component: swing
  • Affected Version: jfx16
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2021-02-24
  • Updated: 2024-03-09
  • Resolved: 2023-08-29
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
jfx22 b07Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
OS Windows 10

Tested on:
* OpenJDK/OpenJFX 8.0.271-b08
* Liberica JDK/JFX combined build 15.0.1+b01
* OpenJDK JDK 17 early access build 10 with OpenJFX 16 build 7

A DESCRIPTION OF THE PROBLEM :
When setting the content of a SwingNode, the old content is not garbage collected.

There are some other bugs that appear related, but are marked as resolved and this is definitely not resolved. I'm not sure if this is truly a separate bug, or if one of those bugs should be reopened. Here are the related bugs:
* https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8093738
* https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8241972
* https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8218124

So far my debugging (using JDK 1.8.0.271) has shown the references to old content are kept alive in a static list of windows in the GlassStage class. The should be removed when close() is called on the JComponent, but this doesn't seem to be called via SwingNode.setContent().

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the following program listed in the "source code for an executable test case" section.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Should print that there are 1 or 2 panels in memory. Possibly a few more but the number should not trend upward over time.
ACTUAL -
Prints:

Panels in memory: 0
Panels in memory: 1
Panels in memory: 2
Panels in memory: 3
Panels in memory: 4
Panels in memory: 5
Panels in memory: 6
Panels in memory: 7
Panels in memory: 8
...

And goes on forever (or I suppose until it runs out of memory, but I didn't wait that long).

---------- BEGIN SOURCE ----------
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;


public class LeakDemo extends Application {

    //Keep week references to all panels that we've ever generated to see if any
    //of them get collected.
    private Collection<WeakReference<JPanel>> panels = new CopyOnWriteArrayList<>();
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        
        SwingNode node = new SwingNode();
        
        Pane root = new Pane();
        root.getChildren().add(node);
        
        //Kick off a thread that repeatedly creates new JPanels and resets the swing node's content
        new Thread(() -> {
            
            while(true) {
                
                //Lets throw in a little sleep so we can read the output
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                SwingUtilities.invokeLater(() -> {
                    JPanel panel = new JPanel();
                    panels.add(new WeakReference<>(panel));
                    node.setContent(panel);
                });
                
                System.out.println("Panels in memory: " + panels.stream().filter(ref -> ref.get() != null).count());
                
                //I know this doesn't guarantee anything, but prompting a GC gives me more confidence that this
                //truly is a bug.
                System.gc();
            }
            
        }).start();
        
        primaryStage.setScene(new Scene(root, 100, 100));
        
        primaryStage.show();
        
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
If you place a JComponent (such as a JPanel) as the content of the swing node, and then update the content of that JComponent rather than setting the SwingNode's content directly, it will collect the old content appropriately. See https://stackoverflow.com/questions/66270491/content-of-swingnode-not-garbage-collected-when-content-changed

FREQUENCY : always



Comments
Changeset: beca88c1 Author: Prasanta Sadhukhan <psadhukhan@openjdk.org> Date: 2023-08-29 03:48:43 +0000 URL: https://git.openjdk.org/jfx/commit/beca88c16fa0942ce7ed32530c411ed9671ad925
29-08-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jfx/pull/1219 Date: 2023-08-22 09:54:11 +0000
22-08-2023

More Discussion here: https://stackoverflow.com/questions/66270491/content-of-swingnode-not-garbage-collected-when-content-changed
01-03-2021

Checked with attached testcase in Windows 10, Issue is reproducible Test Result: ========= 8u281: Fail Openjfx 11: Fail Openjfx 15: Fail Openjfx 16: Fail
01-03-2021