内存泄漏与摆动拖放

时间:2010-05-11 06:49:09

标签: java swing memory-leaks drag-and-drop

我有一个接受顶级文件丢弃的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。

3 个答案:

答案 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),它也没有被清除。