Java 内存泄漏与Swing拖放

Java 内存泄漏与Swing拖放,java,swing,memory-leaks,drag-and-drop,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.Tr

我有一个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);
    }
}
import java.awt.datatransfer.DataFlavor;
导入java.io.File;
导入java.util.List;
导入javax.swing.JFrame;
导入javax.swing.JLabel;
导入javax.swing.TransferHandler;
公共类DnDLeakTester扩展JFrame{
公共静态void main(字符串[]args){
新的DnDLeakTester();
//阻止main返回,否则jvm将退出
while(true){
试一试{
睡眠(10000);
}捕捉(中断异常e){
}
}
}
公共DnDLeakTester(){
超级(“我漏水”);
添加(新JLabel(“在此处放置内容”);
setTransferHandler(新的TransferHandler(){
@凌驾
公共布尔值canImport(最终传输支持){
return(support.isDrop()&&support
.isDataFlavorSupported(DataFlavor.javaFileListFlavor));
}
@凌驾
公共布尔输入数据(最终传输支持){
如果(!canImport(支持)){
返回false;
}
试一试{
最终列表文件=(列表)
support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
用于(最终文件f:文件){
System.out.println(f.getName());
}
}捕获(例外e){
e、 printStackTrace();
}
返回true;
}
});
setDefaultCloseOperation(在关闭时处理);
包装();
setVisible(真);
}
}
要复制,请运行代码并在帧上放置一些文件。关闭框架,以便将其处理掉

为了验证泄漏,我使用JConsole获取一个堆转储,并使用进行分析。它显示sun.awt.AppContext通过其hashmap持有对帧的引用。看来这是他的错

我做错了什么?我是否应该要求DnD支持代码以某种方式进行自我清理


我正在运行JDK 1.6 update 19。

如果将其添加到类中,是否会更改结果

@Override
public void dispose()
{
    setTransferHandler(null);
    setDropTarget(null);    // New
    super.dispose();
}
更新:
我又给电话添加了一个。我认为将drop target设置为null应该会释放更多的引用。我在组件上看不到任何其他可用于获取DnD代码以释放组件的内容。

在搜索了相关类的源代码后,我确信这是TransferHandler中不可避免的漏洞

AppContext映射中的对象DropHandler永远不会被删除。由于键是DropHandler.class对象,而DropHandler是一个私有的内部类,因此从TransferHandler外部删除它的唯一方法是清空整个映射,或者使用反射技巧


DropHandler保存对TransferSupport对象的引用,该对象永远不会被清除。TransferSupport对象持有对组件的引用(在我的例子中是JFrame),该引用也没有被清除。

尽管DropHandler没有从静态AppContext映射中删除,但这并不是真正的根本原因,而只是链中的一个原因。(drop处理程序旨在成为一个单例处理程序,在卸载AppContext类之前不会被清除,实际上从来不会如此。)单例DropHandler的使用是设计的

泄漏的真正原因是DropHandler设置了TransferSupport的实例,该实例可在每个DnD操作中重用,并在DnD操作期间为其提供对DnD中涉及的组件的引用。问题在于,当DnD完成时,不会清除引用。如果在DnD退出时DropHandler调用了
TransferSupport.setDNDVariables(null,null)
,那么问题就会消失。这也是最符合逻辑的解决方案,因为只有在进行DnD时才需要引用组件。其他方法,如清除AppContext映射,则绕过了设计,而不是修复一个小疏忽

但即使我们解决了这个问题,帧仍然不会被收集。不幸的是,似乎还有另一个问题:当我注释掉所有与DnD相关的代码,简化为一个简单的JFrame时,这也没有被收集。保留引用位于
javax.swing.BufferStrategyPaintManager
中。对于这一点,目前还没有确定答案

因此,如果我们修复了DnD,我们将遇到另一个重新绘制的保留问题。幸运的是,所有这些bug只保留了一个帧(希望是同一帧!),所以它并没有那么糟糕。帧被释放,因此本机资源被释放,所有内容都可以被删除,从而允许释放,从而降低内存泄漏的严重性

所以,为了最终回答您的问题,您没有做错任何事情,您只是给JDK中的一些bug一点播放时间

更新:重新绘制管理器错误有一个快速修复方法-添加

-Dswing.bufferPerWindow=false
使用jvm启动选项可以避免该错误。在消除此错误后,发布DnD错误的修复程序是有意义的:

要解决DnD问题,可以在importData()的末尾添加对此方法的调用


谢谢你的建议,德文郡。不,恐怕这不会改变什么。对不起,还是没有运气。我还尝试了getDropTarget().setComponent(null),但它也不起作用。问题是TransferHandler或其内部类中没有任何代码可以从AppContext中删除DropHandler。只是没有一个remove()来补充put(),似乎没有任何方法可以释放App持有的引用
            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)
                {
                }
            }