15

我认为我的 Android 动态壁纸存在内存泄漏。每当我旋转屏幕时,收集的内存垃圾量就会增加 50kb 并且不会减少。我认为这可能是由预定的未来引起的,所以我将提出一个场景来看看是否是这种情况。

假设您有一个具有以下成员的类(我们称之为 Foo)。

private ScheduledFuture<?> future;
private final ScheduledExecutorService scheduler = Executors
        .newSingleThreadScheduledExecutor();

private final Runnable runnable = new Runnable() {
    public void run() {
        // Do stuff
    }
};

现在你设定了一个预定的未来

future = scheduler.scheduleAtFixedRate(runnable, delay, speed,
                TimeUnit.MILLISECONDS);

future 持有对 runnable 的引用,runnable 持有对父 Foo 对象的引用。我不确定是否是这种情况,但这个事实是否意味着如果程序中没有任何内容包含对 Foo 的引用,那么垃圾收集器仍然无法收集它,因为有一个预定的未来?我不太擅长多线程,所以我不知道我显示的代码是否意味着计划任务的寿命会比对象长,这意味着它最终不会被垃圾收集。

如果这种情况不会导致阻止 Foo 被垃圾收集,我只需要通过一个简单的解释来告诉我。如果它确实阻止了 Foo 被垃圾收集,那么我该如何解决呢?一定要做future.cancel(true); future = null;吗?该future = null部分是不必要的吗?

4

3 回答 3

6
  • 您的run方法依赖于封闭Foo类,因此不能独立存在。在那种情况下,我看不出你如何让你的Foogc'ed 并让你的可运行“活着”由执行者运行
  • 或者您的run方法是静态的,因为它不依赖于您的Foo类的状态,在这种情况下,您可以将其设为静态,它将防止您遇到的问题。

您似乎没有处理 Runnable 中的中断。这意味着即使您调用future.cancel(true)Runnable 将继续运行,正如您确定的那样,这可能是导致泄漏的原因。

有几种方法可以使 Runnable “中断友好”。要么你调用一个抛出 InterruptedException 的方法(比如Thread.sleep()阻塞 IO 方法),它会InterruptedException在未来被取消时抛出一个。在清理完需要清理的内容并恢复中断状态后,您可以捕获该异常并立即退出 run 方法:

public void run() {
    while(true) {
        try {
            someOperationThatCanBeInterrupted();
        } catch (InterruptedException e) {
            cleanup(); //close files, network connections etc.
            Thread.currentThread().interrupt(); //restore interrupted status
        }
    }
}    

如果您不调用任何此类方法,则标准习语是:

public void run() {
    while(!Thread.currentThread().isInterrupted()) {
        doYourStuff();
    }
    cleanup();
}

在这种情况下,您应该尝试确保定期检查 while 中的条件。

通过这些更改,当您调用 时future.cancel(true),将向执行 Runnable 的线程发送一个中断信号,该线程将退出正在执行的操作,从而使您的 Runnable 和您的 Foo 实例有资格进行 GC。

于 2012-11-23T18:19:15.557 回答
6

虽然这个问题很久以前就得到了回答,但在阅读了这篇文章之后,我想到了发布新的答案和解释。

预定的未来会导致内存泄漏吗? - - 是的

ScheduledFuture.cancel()或者Future.cancel()通常不会通知它Executor它已被取消并留在队列中,直到它的执行时间到来。对于简单的 Futures 来说这不是什么大问题,但对于ScheduledFutures. 它可以在那里停留几秒钟、几分钟、几小时、几天、几周、几年或几乎无限期,具体取决于它所安排的延迟。

这是最坏情况的示例。可运行对象及其引用的所有内容都将在队列中保留 Long.MAX_VALUE 毫秒,即使其 Future 已被取消!

public static void main(String[] args) {
    ScheduledThreadPoolExecutor executor 
        = new ScheduledThreadPoolExecutor(1);

    Runnable task = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello World!");
        }
    };

    ScheduledFuture future 
        = executor.schedule(task, 
            Long.MAX_VALUE, TimeUnit.MILLISECONDS);

    future.cancel(true);
}

您可以通过使用 aProfiler或调用该ScheduledThreadPoolExecutor.shutdownNow()方法来查看这一点,该方法将返回一个包含一个元素的 List(它是被取消的 Runnable)。

这个问题的解决方案是要么编写你自己的 Future 实现,要么不时调用该purge()方法。如果自定义 Executor 工厂解决方案是:

public static ScheduledThreadPoolExecutor createSingleScheduledExecutor() {
    final ScheduledThreadPoolExecutor executor 
        = new ScheduledThreadPoolExecutor(1);

    Runnable task = new Runnable() {
        @Override
        public void run() {
            executor.purge();
        }
    };

    executor.scheduleWithFixedDelay(task, 30L, 30L, TimeUnit.SECONDS);

    return executor;
}
于 2014-07-16T18:07:43.790 回答
0

future 持有对 runnable 的引用,runnable 持有对父 Foo 对象的引用。我不确定是否是这种情况,但这个事实是否意味着如果程序中没有任何内容包含对 Foo 的引用,那么垃圾收集器仍然无法收集它,因为有一个预定的未来?

Foo制作您经常创建的某种瞬态对象是一个坏主意,因为您应该ScheduledExecutorService scheduler在应用关闭时关闭。因此,您应该使 Foo 成为伪单例。

垃圾收集器了解循环,因此一旦您关闭Foo的执行程序服务,您很可能不会遇到内存问题。

于 2012-11-23T19:36:33.633 回答