簡體   English   中英

預定的未來會導致內存泄漏嗎?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM