Content of SwingNode not garbage collected when content changed

NateW

I have a JavaFX application that displays a Swing plot via a SwingNode. Due to the way that the swing component is written and my unwillingness to refactor it, I create a new instance of the swing plot each time the user needs to update the data. In other words, whenever the user re-generates the plot, it creates a new Swing component and sets the SwingNode's content to the new component.

It all works fine except that I discovered that the swing components never get garbage collected. They contain a substantial amount of data, so after a while it becomes a pretty severe memory leak.

I've been able to demonstrate the issue with this minimum reproducible example:

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);
    }
}

The output of the program is

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
Panels in memory: 9
Panels in memory: 10

and will continue like that into the thousands.

I tried inspecting a heap dump from jvisualvm, but got pretty lost in the sea of references.

I'm suspicious that this is an issue in JavaFX, but I thought I'd check here before I report it as a bug.

NateW

OK, I figured it out.

Short answer

Just wrap the swing content inside a JPanel (or some other JComponent). Then only ever call SwingNode.setContent() once to add the wrapper. When you need to update the swing content, call removeAll() on your wrapper and then add() with the appropriate content.

Long Answer

Thanks to the suggestion in this answer: https://stackoverflow.com/a/66283491/2423283 I was able to determine that the leak is caused by GlassStage which is a non-api class which among other things, keeps a static list of all implementations of GlassStage. The content of a SwingNode gets managed by an instance of EmbeddedScene which is a subtype of GlassStage.

Items are removed from the static list when close() is called on them. SwingNode.setContent() does not close any pre-existing content, but Container.removeAll() does.

Working code

Here's an example of the fixed code:

public class LeakDemoFixed 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();
        
        //These 2 lines were added
        JComponent swingContent = new JPanel();
        node.setContent(swingContent);
        
        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));
                    //Removed the line below
                    //node.setContent(panel);
                    
                    //Added these 2 lines
                    swingContent.removeAll();
                    swingContent.add(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);
    }
}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Java

When will a string be garbage collected in java

From Dev

When are constants garbage collected in Ruby?

From Dev

When are JavaScript Blob objects garbage collected?

From Dev

Has Directory Content Changed?

From Dev

When JavaFX tab should be collected by garbage collector?

From Dev

Is this garbage collected?

From Dev

SoftReference<String> not garbage collected when out of memory

From Dev

Content overlapping in html page, when windows size is changed

From Dev

EmberJS: undefined is not function on helper when content changed

From Dev

Event for ckeditor content changed

From Dev

SwingNode with transparent content

From Dev

When are python classes and class attributes garbage collected?

From Dev

android Recyclerview Layoutmanager's onLayoutChildren called when item content is changed

From Dev

if condition changed by its content

From Dev

Method to refresh Fragment content when data changed ( like recall onCreateView)

From Dev

When Will A Local Unattached DOM Element Be Garbage Collected?

From Dev

When is this scope/closure being garbage collected in javaScript?

From Dev

When sockets are dereferenced, are they garbage collected

From Dev

Garbage when loading xml content with URLConnection

From Dev

How does a value in an entry in the WeakHashMap gets garbage collected when the actual object is garbage collected?

From Dev

Content of SwingNode not garbage collected when content changed

From Dev

replace body content when footer links have changed

From Dev

SoftReference<String> not garbage collected when out of memory

From Dev

page content when tab changed in chrome extension

From Dev

Removing content from a set of inputs when input has changed

From Dev

Indication whether the content is changed

From Dev

c++ array content changed when refereing to global array

From Dev

Copy file with changed content

From Dev

Garbage when loading xml content with URLConnection

Related Related

  1. 1

    When will a string be garbage collected in java

  2. 2

    When are constants garbage collected in Ruby?

  3. 3

    When are JavaScript Blob objects garbage collected?

  4. 4

    Has Directory Content Changed?

  5. 5

    When JavaFX tab should be collected by garbage collector?

  6. 6

    Is this garbage collected?

  7. 7

    SoftReference<String> not garbage collected when out of memory

  8. 8

    Content overlapping in html page, when windows size is changed

  9. 9

    EmberJS: undefined is not function on helper when content changed

  10. 10

    Event for ckeditor content changed

  11. 11

    SwingNode with transparent content

  12. 12

    When are python classes and class attributes garbage collected?

  13. 13

    android Recyclerview Layoutmanager's onLayoutChildren called when item content is changed

  14. 14

    if condition changed by its content

  15. 15

    Method to refresh Fragment content when data changed ( like recall onCreateView)

  16. 16

    When Will A Local Unattached DOM Element Be Garbage Collected?

  17. 17

    When is this scope/closure being garbage collected in javaScript?

  18. 18

    When sockets are dereferenced, are they garbage collected

  19. 19

    Garbage when loading xml content with URLConnection

  20. 20

    How does a value in an entry in the WeakHashMap gets garbage collected when the actual object is garbage collected?

  21. 21

    Content of SwingNode not garbage collected when content changed

  22. 22

    replace body content when footer links have changed

  23. 23

    SoftReference<String> not garbage collected when out of memory

  24. 24

    page content when tab changed in chrome extension

  25. 25

    Removing content from a set of inputs when input has changed

  26. 26

    Indication whether the content is changed

  27. 27

    c++ array content changed when refereing to global array

  28. 28

    Copy file with changed content

  29. 29

    Garbage when loading xml content with URLConnection

HotTag

Archive