预定的未来会导致内存泄漏吗?
Posted
技术标签:
【中文标题】预定的未来会导致内存泄漏吗?【英文标题】:Can a scheduled future cause a memory leak? 【发布时间】:2012-11-12 02:39:21 【问题描述】:我认为我的 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
部分是不必要的吗?
【问题讨论】:
【参考方案1】:虽然这个问题很久以前就得到了回答,但在阅读了this 文章后,我想发布新的答案并附上解释。
预定的未来会导致内存泄漏吗? --- 是的
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);
您可以通过使用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;
【讨论】:
您能否解释一下,在这种情况下会发生什么?这是一个单线程执行器。如果 Runnable 任务是长时间运行的任务(比如说一天)并且延迟和周期与上面相同。任务是否排队?任务队列有上限吗?【参考方案2】:future 持有对 runnable 的引用,runnable 持有一个 对父 Foo 对象的引用。我不确定是否是这种情况, 但是这个事实是否意味着如果程序中没有任何内容 引用 Foo,垃圾收集器仍然无法收集它 因为有一个预定的未来?
将Foo
制作成您经常创建的某种临时对象是个坏主意,因为您应该在应用关闭时关闭ScheduledExecutorService scheduler
。因此,您应该使 Foo 成为伪单例。
垃圾收集器了解循环,因此一旦您关闭 Foo
的执行程序服务,您很可能不会遇到内存问题。
【讨论】:
【参考方案3】: 您的run
方法依赖于封闭的Foo
类,因此不能独立存在。在那种情况下,我看不出你如何让你的Foo
gc'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。
【讨论】:
我的 run 方法确实依赖于封闭的 Foo 类。但是,在我销毁对 Foo 对象的引用之前,我必须做些什么来阻止可运行对象导致内存泄漏。future.cancel(true)
就足够了吗?我已经这样做了,内存泄漏仍然存在。当然,这可能意味着 runnable 不是泄漏源。
@gsingh2011 你确定future.cancel(true)
会取消你的未来吗?换句话说,你的 runnable 可以被中断吗?当它被中断时它会终止它的工作吗?
因为我不明白“你的 runnable 可以被打断是什么意思”,我假设它不能被打断。我该怎么做?以上是关于预定的未来会导致内存泄漏吗?的主要内容,如果未能解决你的问题,请参考以下文章