【发布时间】:2021-02-19 01:20:37
【问题描述】:
我有一个通过 SwingNode 显示 Swing 图的 JavaFX 应用程序。由于摆动组件的编写方式以及我不愿意对其进行重构,因此每次用户需要更新数据时,我都会创建一个新的摆动图实例。换句话说,每当用户重新生成绘图时,它都会创建一个新的 Swing 组件并将 SwingNode 的内容设置为新组件。
除了我发现摆动组件永远不会被垃圾收集之外,一切都很好。它们包含大量数据,因此一段时间后它会成为非常严重的内存泄漏。
我已经能够通过这个最小的可重现示例来证明问题:
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 中的一个问题,但我想在将其报告为错误之前先检查一下。
【问题讨论】:
-
如果你删除了弱引用列表等,内存泄漏是否仍然发生?如果你只是构建面板并添加弱引用,引用会被清除吗?
-
当您通过调用
revalidate()告诉容器更新其引用时,行为是否仍然相同? -
@Charlie 是的,如果我删除 WeakReference 的东西,泄漏仍然会发生。它只是有点难以演示,因为我必须查看 jvisualvm 中的堆转储。
-
@Koenigsberg,JavaFX 组件都没有
revalidate()方法,但swing 组件有。我尝试获取旧的 SwingNode 内容,然后在将其替换为新内容后调用revalidate(),但这并没有成功。 -
我从未在 visualvm 中调试过内存泄漏,但(非常昂贵)jprofiler 非常有用。您可以简单地找到所有 JPanel 并追溯到 GC 根。