java - 内容更改时 SwingNode 的内容未被垃圾收集

标签 java swing javafx

我有一个 JavaFX 应用程序,它通过 SwingNode 显示 Swing 图。由于 swing 组件的编写方式以及我不愿意重构它,每次用户需要更新数据时,我都会创建一个新的 swing plot 实例。换句话说,每当用户重新生成绘图时,它都会创建一个新的 Swing 组件并将 SwingNode 的内容设置为新组件。

一切正常,只是我发现 swing 组件永远不会被垃圾回收。它们包含大量数据,因此一段时间后会变成非常严重的内存泄漏。

我已经能够用这个最小的可重现示例来证明这个问题:

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

程序的输出是

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

并且会继续这样发展到成千上万。

我尝试检查来自 jvisualvm 的堆转储,但在引用文献的海洋中迷失了方向。

我怀疑这是 JavaFX 中的一个问题,但我想在将其报告为错误之前先检查一下这里。

最佳答案

好的,我明白了。

简答

只需将 swing 内容包装在 JPanel(或其他一些 JComponent)中。然后只调用一次 SwingNode.setContent() 来添加包装器。当您需要更新 swing 内容时,调用包装器上的 removeAll(),然后使用适当的内容调用 add()

长答案

感谢此答案中的建议:https://stackoverflow.com/a/66283491/2423283我能够确定泄漏是由 GlassStage 引起的,它是一个非 API 类,除其他外,它保留了 GlassStage 所有实现的静态列表。 SwingNode 的内容由 EmbeddedScene 的实例管理,该实例是 GlassStage 的子类型。

当调用 close() 时,项目将从静态列表中删除。 SwingNode.setContent() 不会关闭任何预先存在的内容,但 Container.removeAll() 会。

工作代码

这是固定代码的示例:

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

关于java - 内容更改时 SwingNode 的内容未被垃圾收集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66270491/

相关文章:

java - 从 android studio 中的图库获取图像 - 不相关的代码

java - OpenCV,拉普拉斯算子的变体(Java)

java - 匹配正则表达式模式时如何忽略变音符号?

java - 用Java填充一个矩形

java - 从其他 JFrame 控制 JCheckBox

css - JavaFx 仅限顶级innerShadow

java - 通过 Eclipse 从 Java(FX) 创建 Windows EXE

java - Akka持久化自定义java插件

java - 执行这个 do while 的正确方法是什么?

java - 如何链接这两个鼠标事件? JavaFX