我有一个接受顶级文件丢弃的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通过其hashmap持有对帧的引用。看起来TransferSupport有问题。
image of path to GC root http://img402.imageshack.us/img402/4444/dndleak.png
我做错了什么?我应该要求DnD支持代码以某种方式清理自己吗?
我正在运行JDK 1.6更新19。
答案 0 :(得分:4)
虽然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,我们会重新绘制另一个保留问题。幸运的是,所有这些bug只能保留一个帧(希望是同一个帧!),所以它并没有那么糟糕。框架已被释放,因此释放了本机资源,并且可以删除所有内容,从而可以释放内容,从而降低内存泄漏的严重性。
所以,为了最终回答你的问题,你没有做错什么,你只是在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)
{
}
}
答案 1 :(得分:1)
如果你把它添加到你的班级,它会改变你的结果吗?
@Override
public void dispose()
{
setTransferHandler(null);
setDropTarget(null); // New
super.dispose();
}
<强>更新强> 我已经添加了另一个对dispose的调用。我认为将drop target设置为null应该会释放更多引用。我在组件上看不到任何可用于让DnD代码放开组件的其他内容。
答案 2 :(得分:0)
在仔细检查了相关类的来源之后,我确信这是TransferHandler中不可避免的泄漏。
永远不会删除AppContext地图中的对象DropHandler。由于键是DropHandler.class对象,而DropHandler是私有内部类,因此可以从TransferHandler外部删除它的唯一方法是清空整个映射,或者使用反射技巧。
DropHandler保存对TransferSupport对象的引用,该对象永远不会被清除。 TransferSupport对象包含对组件的引用(在我的例子中是一个JFrame),它也没有被清除。