Swing 拖放导致内存泄漏
Posted
技术标签:
【中文标题】Swing 拖放导致内存泄漏【英文标题】:Memory Leak with Swing Drag and Drop 【发布时间】:2011-02-18 00:16:16 【问题描述】:我有一个接受***文件的 JFrame。然而,在发生丢弃之后,对框架的引用会无限期地保存在一些 Swing 内部类中。我认为处置框架应该释放其所有资源,那么我做错了什么?
例子
import java.awt.datatransfer.DataFlavor;
import java.io.File;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.TransferHandler;
public class DnDLeakTester extends JFrame
public static void main(String[] args)
new DnDLeakTester();
//Prevent main from returning or the jvm will exit
while (true)
try
Thread.sleep(10000);
catch (InterruptedException e)
public DnDLeakTester()
super("I'm leaky");
add(new JLabel("Drop stuff here"));
setTransferHandler(new TransferHandler()
@Override
public boolean canImport(final TransferSupport support)
return (support.isDrop() && support
.isDataFlavorSupported(DataFlavor.javaFileListFlavor));
@Override
public boolean importData(final TransferSupport support)
if (!canImport(support))
return false;
try
final List<File> files = (List<File>)
support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
for (final File f : files)
System.out.println(f.getName());
catch (Exception e)
e.printStackTrace();
return true;
);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setVisible(true);
要重现,运行代码并将一些文件放在框架上。关闭框架,以便将其处理掉。
为了验证泄漏,我使用 JConsole 进行堆转储并使用 Eclipse Memory Analysis tool 对其进行分析。它表明 sun.awt.AppContext 通过其哈希图持有对框架的引用。看起来 TransferSupport 有问题。
image of path to GC root http://img402.imageshack.us/img402/4444/dndleak.png
我做错了什么?我应该要求 DnD 支持代码以某种方式自行清理吗?
我正在运行 JDK 1.6 更新 19。
【问题讨论】:
我开始认为这是一个 JVM 错误。相关的 DnD 类没有清除违规引用的代码,因此除非以某种方式从 AppContext 的映射中删除 DropHandler(我不太了解该类),否则泄漏将继续存在。 这个论坛帖子描述了一个类似的问题 [forums.java.net/jive/thread.jspa?messageID=276311].它没有得到任何回应。 【参考方案1】:虽然 DropHandler 没有从静态 AppContext 映射中删除,但这并不是真正的根本原因,而只是链条中的原因之一。 (drop 处理程序旨在成为一个单例,并且在 AppContext 类被卸载之前不会被清除,这在实践中永远不会。)使用单例 DropHandler 是设计使然。
泄漏的真正原因是 DropHandler 设置了一个 TransferSupport 的实例,该实例被每个 DnD 操作重用,并在 DnD 操作期间为其提供对 DnD 中涉及的组件的引用。问题是当 DnD 完成时它不会清除引用。如果DnD 退出时DropHandler 调用TransferSupport.setDNDVariables(null,null)
,那么问题就会消失。这也是最合乎逻辑的解决方案,因为仅在 DnD 进行时才需要对组件的引用。其他方法(例如清除 AppContext 映射)是在规避设计,而不是修复一个小的疏忽。
但是即使我们解决了这个问题,框架仍然不会被收集。不幸的是,似乎还有另一个问题:当我注释掉所有与 DnD 相关的代码时,减少到只是一个简单的 JFrame,这也没有被收集。重制参考位于javax.swing.BufferStrategyPaintManager
。对此有一个bug report,尚未修复。
因此,如果我们修复 DnD,我们会遇到另一个与重新绘制有关的保留问题。幸运的是,所有这些错误都只保留一帧(希望是同一帧!),所以它并没有想象的那么糟糕。框架被释放,因此本地资源被释放,所有内容都可以被移除,从而被释放,从而降低内存泄漏的严重性。
所以,最后回答你的问题,你并没有做错什么,你只是给 JDK 中的一些错误一点时间!
更新:重绘管理器错误有一个快速修复 - 添加
-Dswing.bufferPerWindow=false
到jvm启动选项避免了这个bug。消除此错误后,发布 DnD 错误的修复程序是有意义的:
要解决 DnD 问题,您可以在 importData() 的末尾添加对该方法的调用。
private void cancelDnD(TransferSupport support)
/*TransferSupport.setDNDVariables(Component component, DropTargetEvent event)
Call setDNDVariables(null, null) to free the component.
*/
try
Method m = support.getClass().getDeclaredMethod("setDNDVariables", new Class[] Component.class, DropTargetEvent.class );
m.setAccessible(true);
m.invoke(support, null, null);
System.out.println("cancelledDnd");
catch (Exception e)
【讨论】:
感谢您的信息。我发现 BufferStrategyPaintManager 保留了一些类,但被 DnD 的东西分心了。我接受反射技巧会起作用 - 但我不打算使用它 当然,是否使用修复程序是您的选择,但现在至少您知道问题的范围并可以决定如何处理它。喜欢你。我可能不会费心添加修复程序,并且知道可以部分缓解问题,可能什么都不做,或者最多从处置的框架中删除内容。但事后看来,现在问题的严重程度是已知的。我很想知道为什么会发生泄漏以及后果是什么,并且很高兴知道如果我们突然发现需要修复,那是可用的。今晚我可以睡个好觉了! :-)【参考方案2】:如果你把它添加到你的班级,它会改变你的结果吗?
@Override
public void dispose()
setTransferHandler(null);
setDropTarget(null); // New
super.dispose();
更新: 我在 dispose 中添加了另一个调用。我认为将放置目标设置为 null 应该会释放更多引用。我在组件上看不到任何其他可用于获取 DnD 代码以释放您的组件的内容。
【讨论】:
感谢您的建议德文。不,恐怕这不会改变任何事情。 抱歉,还是没有运气。我也尝试过 getDropTarget().setComponent(null),但它也不起作用。问题是在 TransferHandler 或其内部类中没有任何代码可以从 AppContext 中删除 DropHandler。只是没有 remove() 来补充 put() 似乎没有任何方法可以释放 AppContext 持有的引用。幸运的是,put 中使用的 key 是 DropTarget 的 Class,所以只能有 1 个未完成的实例。记录它并通过 dispose() 释放其他所有内容来最小化损坏。比如getContentPane().removeAll()。【参考方案3】:在搜索了相关类的来源后,我确信这是 TransferHandler 中不可避免的泄漏。
AppContext 映射中的对象 DropHandler 永远不会被删除。由于键是 DropHandler.class 对象,并且 DropHandler 是一个私有内部类,因此可以从 TransferHandler 外部删除它的唯一方法是清空整个地图,或者使用反射技巧。
DropHandler 持有对 TransferSupport 对象的引用,该对象永远不会被清除。 TransferSupport 对象包含对组件的引用(在我的例子中是一个 JFrame),它也没有被清除。
【讨论】:
以上是关于Swing 拖放导致内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章