2010-05-11 86 views
7

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

+0

我开始认为这是一个JVM错误。相关的DnD类没有代码来清除有问题的引用,所以除非DropHandler以某种方式从AppContext的映射中移除(我并不真正了解该类),否则泄漏将保留。 – tom 2010-05-12 00:32:54

+0

这个论坛帖子描述了一个类似的问题[http://forums.java.net/jive/thread.jspa?messageID=276311]。它没有回应。 – tom 2010-05-12 00:47:17

回答

4

尽管DropHandler并未从静态AppContext映射中移除,但这并非真正的根本原因,而仅仅是链中的原因之一。 (Drop处理器是一个单例,在AppContext类被卸载之前不会被清除,实际上它永远不会被使用。)使用单例DropHandler是有意设计的。

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

但即使我们解决这个问题,框架仍然不会收集。不幸的是,似乎还有另外一个问题:当我将所有与DnD相关的代码注释掉时,简化为仅仅是一个简单的JFrame,这也没有被收集。减少的参考文献在javax.swing.BufferStrategyPaintManager。有一个bug report为此,尚未确定。

因此,如果我们修复了DnD,我们在重绘时遇到了另一个保留问题。幸运的是,所有这些bug都只能保留一帧(希望是同一个!),所以它不会那么糟糕。该框架被放置,所以本地资源被释放,并且所有内容都可以被移除,从而释放内存,从而降低内存泄漏的严重性。因此,为了最终回答您的问题,您没有做错任何事情,您只是在JDK中稍微空出一些错误!

UPDATE:重绘经理臭虫有速战速决 - 添加

-Dswing.bufferPerWindow=false 

到JVM启动选项避免错误。有了这个bug后,修复DnD bug是很有意义的:

要解决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) 
       { 
       } 
      } 
+0

感谢您的信息。我发现BufferStrategyPaintManager保留了一些类,但是被DnD的东西分散了。我接受反思技巧会起作用 - 但我不打算使用它 – tom 2010-05-18 23:01:18

+0

当然,如果您使用修复或不修复,这是您的选择,但现在至少您知道问题的范围并可以决定如何处理它。喜欢你。我可能不打扰添加修复程序,并知道问题可以部分缓解,可能什么也不做,或者至多从处理帧中删除内容。但现在事后看来,问题的严重程度是已知的。我很想知道泄漏发生的原因以及后果是什么,很高兴知道如果我们突然发现需要修复,那么可以使用该修复。今晚我可以轻松入眠! :-) – mdma 2010-05-18 23:14:07

1

它是否在改变你的结果,如果你添加到您的课吗?

@Override 
public void dispose() 
{ 
    setTransferHandler(null); 
    setDropTarget(null); // New 
    super.dispose(); 
} 

更新: 我添加了另一个调用处置。我认为将放置目标设置为空应释放更多的引用。我没有看到组件上可以用来让DnD代码放开你的组件的其他任何东西。

+0

谢谢你的建议德文郡。不,我担心它不会改变任何事情。 – tom 2010-05-12 00:28:00

+0

对不起,仍然没有运气。我也试过getDropTarget()。setComponent(null),但它也不起作用。问题是TransferHandler或其内部类中没有任何代码从AppContext中删除DropHandler。只是没有remove()来补充put() – tom 2010-05-12 23:47:34

+0

似乎没有任何方法可以释放AppContext持有的引用。幸运的是,put中使用的关键字是DropTarget类,因此只能有一个未完成的实例。记录它并通过处置()释放其他所有东西来最小化损害。像getContentPane()。removeAll()。 – 2010-05-13 16:54:58

0

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

AppContext的地图中的对象DropHandler永远不会被删除。由于关键是DropHandler.class对象,并且DropHandler是一个私有的内部类,所以可以从TransferHandler外部移除的唯一方式是清空整个地图或使用反射技巧。

DropHandler持有对TransferSupport对象的引用,该对象永远不会被清除。 TransferSupport对象持有对组件的引用(在我的情况下是一个JFrame),它也不会被清除。