2013-04-09 163 views
10

在測試我們的Javascript庫時,我認爲我們在IE10(v10.0.9200.16519 - Windows 8 64位)Javascript實現setInterval中發現了嚴重的內存泄漏。解決方法IE10 setInterval內存泄漏

一個簡單的測試案例表明,如果一個變量在作爲參數傳遞的函數的閉包中被捕獲以供後續執行,它似乎沒有資格進行垃圾回收,即瀏覽器似乎仍然持有引用到函數或者至少是閉包變量。

我們的測試用例執行setInterval功能只有一次,然後沒有代碼被運行了一段時間後,清除間隔定時器,即沒有變量訪問了(據我可以看到沒有全局介紹了在這段代碼中,除了該方法在onload中運行),但該進程佔用了半個千兆字節的內存(取決於迭代次數)。

有趣的是,如果我們使用setTimeout方法,而不是(也問題不似乎沒有在IE9存在,和目前版本的Chrome,FF)不會發生這種情況。

該問題可以通過this fiddle查看。

在Windows 8上的IE10的新實例中運行它,並打開任務管理器以查看內存使用情況。它將快速增長到350兆字節,並在腳本執行後停留在那裏。

這是有問題的代碼塊的重要組成部分:

// the function that when called multiple times will cause the leak in IE10 
var eatMemory = function() { 
    var a = null; // the captured closure variable 
    var intervalId = setInterval(function() { 
     a = createBigArray(); // call a method that allocates a lot of memory 
     clearInterval(intervalId); // stop the interval timer 
    }, 100); 
} 

(我知道這是很容易解決這個特定一段代碼但是,這不是問題的關鍵 - 這僅僅是最小的。一段代碼,我們想出了能重現問題,真正的代碼實際上捕獲在封閉this和對象是永遠不會被垃圾收集。)

有沒有在我們的代碼中的bug或者有使用setInterval其中一種方式一個閉包變量持有對al的引用arge對象,而不會觸發內存泄漏並且不會恢復爲「遞歸」調用?

(我也posted the question on MSDN

更新:這個問題也存在於IE10在Windows 7上,但如果切換到IE9標準的模式不存在。我將此提交給MS Connect並報告進度。

更新:微軟accepted the issue並報告它被固定在IE11(預覽版) - 我沒有這個確認自己,但

更新: IE 11已經正式發佈(人?)並且我無法使用我的系統(Win 8.1 Pro 64bit)再現該版本的問題。由回落到setTimeout

正如我已經寫了(和評論者建議),這可以被合作周圍(不固定):

+0

你爲什麼要創建一個間隔,然後立即將其清除?這不是破壞目的嗎? – 2013-04-09 15:13:42

+0

@BradM我用一個解釋更新了問題 - 真實的代碼比這更復雜,並不總是無條件地清除間隔。 – Sebastian 2013-04-09 15:18:53

+0

您是否嘗試過不使用匿名函數,而是使用函數引用 - 之後顯式覆蓋此引用? – CBroe 2013-04-09 15:22:00

回答

6

爲了完整起見,我在這裏增加一個可能的解決方法。這不是微不足道的,因爲需要做一些身份證簿記。這裏是我建議的修復方法,你可以test and fork from this fiddle

var registerSetIntervalFix = function(){ 
    var _setTimeout = window.setTimeout; 
    var _clearTimeout = window.clearTimeout; 
    window.setInterval = function(fn, interval){ 
     var recurse = function(){ 
      var newId = _setTimeout(recurse, interval); 
      window.setInterval.mapping[returnValue] = newId; 
      fn(); 
     } 
     var id = _setTimeout(recurse, interval); 
     var returnValue = id; 
     while (window.setInterval.mapping[returnValue]){ 
      returnValue++; 
     } 
     window.setInterval.mapping[returnValue] = id; 
     return returnValue; 
    } 
    window.setInterval.mapping = {}; 
    window.clearInterval = function(id){ 
     var realId = window.setInterval.mapping[id]; 
     _clearTimeout(realId); 
     delete window.setInterval.mapping[id]; 
    } 
} 

的想法是遞歸調用setTimeout模擬重複setInterval電話。在這個實現中有一點花銷,因爲它必須爲變化的id s執行簿記,所以我不建議應用此修補程序,除非它是必需的。

不幸的是我無法想出一個「功能」檢測算法(更像是一個「錯誤」檢測算法),所以我想你必須恢復到舊的瀏覽器檢測。此外,我的實現不能將字符串作爲第一個參數處理,也不會將其他參數傳遞給內部函數。最後,將這種方法稱爲兩次是不安全的,因此使用它需要您自擔風險(並且可以隨意改進它)!

(注:我們的圖書館,我們將停止使用setInterval從現在開始,而是在依靠它來直接使用setTimeout代碼重寫幾部分組成。)