Java 内存泄漏与Swing拖放
我有一个JFrame,它接受顶级的文件拖放。但是,在发生删除之后,对框架的引用将无限期地保留在某些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
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)
{
}
}