[英]Can a scheduled future cause a memory leak?
我想我的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);
將來會對runnable進行引用,而runnable可以保存對父Foo對象的引用。 我不確定是不是這種情況,但這個事實是否意味着如果程序中沒有任何內容引用Foo,垃圾收集器仍然無法收集它,因為有預定的未來? 我不太擅長多線程,所以我不知道我顯示的代碼是否意味着計划任務的壽命比對象長,這意味着它不會被垃圾收集。
如果這種情況不會導致阻止Foo被垃圾收集,我只需要通過一個簡單的解釋告訴它。 如果它確實阻止Foo被垃圾收集,那么我該如何解決? 必須做future.cancel(true); future = null;
future.cancel(true); future = null;
? future = null
部分是否不必要?
run
方法依賴於封閉的Foo
類,因此無法獨立生存。 在那種情況下,我不知道你怎么能擁有你的Foo
gc'ed並讓你的runnable“活着”由執行者運行 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條件。
雖然這個問題很久以前就得到了解答,但在閱讀完這篇文章之后,我想到了解釋的新答案。
預定的未來會導致內存泄漏嗎? ---是的
ScheduledFuture.cancel()
或Future.cancel()
通常不會通知其Executor
它已被取消並且它一直停留在隊列中,直到它的執行時間到來。 對於簡單的Futures來說這不是什么大問題,但對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()
地調用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進行引用,而runnable可以保存對父Foo對象的引用。 我不確定是不是這種情況,但這個事實是否意味着如果程序中沒有任何內容引用Foo,垃圾收集器仍然無法收集它,因為有預定的未來?
將Foo
為您經常創建的某種瞬態對象是一個壞主意,因為您應該在應用關閉時關閉ScheduledExecutorService scheduler
程序。 因此你應該讓Foo成為偽單身人士。
垃圾收集器了解周期,因此一旦關閉Foo
的執行器服務,您很可能不會遇到內存問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.