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

时间:2012-11-23 18:11:43

标签: java android multithreading memory-leaks future

我想我的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部分是否不必要?

3 个答案:

答案 0 :(得分:5)

  • 您的run方法依赖于封闭的Foo类,因此无法独立生活。在那种情况下,我看不出你如何拥有Foo gc'ed并让你的runnable“alive”由执行者运行
  • 或您的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的条件。

答案 1 :(得分:5)

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

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

ScheduledFuture.cancel()Future.cancel()一般不会通知其Executor它已被取消,并且它一直停留在队列中,直到执行时间到来为止。对于简单的期货来说这不是什么大问题,但对于ScheduledFutures来说可能是个大问题。它可以在那里停留数秒,数分钟,数小时,数天,数周,数年或几乎无限期,具体取决于计划的延迟。

以下是最糟糕情况的示例。即使在其Future已被取消之后,runnable及其引用的所有内容都将保留在Queue for Long.MAX_VALUE毫秒内!

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

您可以使用Profiler或调用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;
}

答案 2 :(得分:0)

  

未来持有对runnable的引用,而runnable持有a   引用父Foo对象。我不确定是不是这样,   但这个事实是否意味着如果程序中没有任何内容可以保留   引用Foo,垃圾收集器仍然无法收集它   因为有预定的未来?

Foo某种临时对象经常创建是个坏主意,因为当你的应用关闭时你应该关闭ScheduledExecutorService scheduler。因此,你应该让Foo成为伪单身人士。

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