我有一个 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/