如何避免回调中的内存泄漏?

24

《Effective Java》中提到:

第三个常见的内存泄漏来源是监听器和其他回调函数。如果你实现了一个API,客户端可以注册回调函数,但不显式注销它们,那么这些回调函数就会累积,除非你采取一些措施。确保回调函数及时被垃圾收集的最佳方法是仅存储对它们的弱引用,例如,仅将它们作为WeakHashMap中的键来存储。

我是Java的初学者。有人能教我如何在回调函数中创建弱引用,并告诉我它们如何解决内存泄漏问题吗?谢谢。

答:在Java中创建弱引用可以使用java.lang.ref.WeakReference类。通过将回调函数作为键存储在WeakHashMap中,只有当回调函数没有其他强引用时才会被垃圾回收。由于回调函数被存储在WeakHashMap中,因此它们的生命周期受到该Map的生命周期的影响。当回调函数不再需要时,应该显式地从注册中注销,并且从WeakHashMap中删除其键,以便垃圾回收尽快进行。
3个回答

15

请阅读这篇文章

本文的主要内容如下:

直接引用是指不需要额外编码以创建或访问对象的强引用。剩下的三种引用是java.lang.ref包中Reference类的子类。SoftReference类提供软引用,WeakReference类提供弱引用,PhantomReference类提供虚引用。

软引用类似于数据缓存。当系统内存不足时,垃圾收集器可以任意释放只有软引用的对象。换句话说,如果没有强引用指向一个对象,那么这个对象就可以被释放。在抛出OutOfMemoryException之前,垃圾收集器必须释放所有软引用。

弱引用比软引用更弱。如果一个对象的唯一引用是弱引用,那么垃圾收集器可以随时回收该对象使用的内存。通常,对象使用的内存在下一次垃圾收集器执行时被回收。

虚引用与清理任务有关。它们提供了一种在垃圾收集器执行最终化过程并释放对象之前立即通知的方式。可以将其视为在对象内部执行清理任务的方式。

接下来是WeakListModel列表,为避免混乱,我不会在这里发布。


1
我只想指出链接已经损坏。 - Maurice

10

为了用一个简单(粗略)的例子来说明这个概念,请考虑以下内容:

public interface ChangeHandler {
    public void handleChange();
}

public class FileMonitor {

    private File file;
    private Set<ChangeHandler> handlers = new HashSet<ChangeHandler>();

    public FileMonitor(File file) { 
        this.file = file;
    }

    public void registerChangeHandler(ChangeHandler handler) {
        this.handlers.add(handler);
    } 

    public void unregisterChangeHandler(ChangeHandler handler) {
        this.handlers.remove(handler);
    }

    ...
}

如果客户端类使用这个 FileMonitor API,它们可能会这样做:

public class MyClass {

    File myFile = new File(...);
    FileMonitor monitor = new FileMonitor(myFile);

    public void something() {
        ...
        ChangeHandler myHandler = getChangeHandler();
        monitor.registerChangeHandler(myHandler);
        ...
    }
}
如果MyClass的作者在完成处理器后忘记调用unregisterChangeHandler(),则FileMonitorHashSet将永远引用已注册的实例,导致它一直存在内存中,直到销毁FileMonitor或应用程序退出。
为了防止这种情况,Bloch建议使用弱引用集合代替HashSet,这样如果MyClass的实例被销毁,则监视器集合中的引用将被移除。
你可以使用WeakHashMap来替换FileMonitor中的HashSet,并使用处理器作为键,因为后者会在对象的所有其他引用都不存在时自动从集合中删除处理器。

1
谢谢提供的示例,但我仍然不太明白。当something()返回时,强引用myHandler将超出范围。难道WeakHashMap引用的处理程序不会在任何时候被销毁(即使MyClass实例仍然存在)吗?这不是你想要的结果,对吧? - kodu
啊,我开始明白了……你可以使用WeakHashMap<MyClass, ChangeHandler>,这样键实际上就是MyClass的实例。 - kodu
我认为myHandler应该在something()返回后执行,即使MyClass实例仍然存在。因此,WeakHashMap <ChangeHandler,?>比WeakHashMap <MyClass,ChangeHandler>更好。 - yuxh

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接