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

有效的Java说:

内存泄漏的第三个常见来源是侦听器和其他回调。如果实现一个 API,其中客户端注册回调但不显式取消注册回调,则除非您采取某些操作,否则回调将累积。确保及时垃圾回收回调的最佳方法是仅存储对它们的弱引用,例如,仅将它们作为键存储在 WeakHashMap 中。

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


答案 1

阅读本文

关键是:

您可以将直接引用视为强引用,无需额外的编码即可创建或访问对象。其余三种类型的引用是在 java.lang.ref 包中找到的 Reference 类的子类。软引用由 SoftReference 类提供,弱引用由 WeakReference 类提供,幻像引用由 PhantomReference 提供。

软引用的作用类似于数据缓存。当系统内存不足时,垃圾回收器可以任意释放其唯一引用为软引用的对象。换句话说,如果没有对某个对象的强引用,则该对象是发布的候选对象。垃圾回收器需要在抛出 OutOfMemoryException 之前释放任何软引用。

弱引用比软引用弱。如果对对象的唯一引用是弱引用,则垃圾回收器可以随时回收对象使用的内存。对于内存不足的情况,不需要。通常,对象使用的内存将在垃圾回收器的下一阶段回收。

幻像引用与清理任务相关。它们在垃圾回收器执行终结过程并释放对象之前立即提供通知。将其视为在对象内执行清理任务的一种方法。

然后是WeakListModel列表,我不会发布,以避免混乱的响应。


答案 2

要使用快速(粗略)示例来说明概念,请考虑以下事项:

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);
    }

    ...
}

如果客户端类随后使用此 API,则它们可能会执行以下操作:FileMonitor

public class MyClass {

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

    public void something() {
        ...
        ChangeHandler myHandler = getChangeHandler();
        monitor.registerChangeHandler(myHandler);
        ...
    }
}

如果 then 的作者在处理程序完成操作后忘记调用,则 的 将永远引用已注册的实例,导致它保留在内存中,直到 被销毁或应用程序退出。MyClassunregisterChangeHandler()FileMonitorHashSetFileMonitor

为了防止这种情况,Bloch 建议使用弱引用集合而不是 ,这样,如果 实例被销毁,引用将从监视器的集合中删除。HashSetMyClass

您可以将 in 替换为 WeakHashMap,并使用处理程序作为键,因为当对对象的所有其他引用都消失时,后者将自动从集合中删除处理程序。HashSetFileMonitor


推荐