IE10 setInterval内存泄漏的解决方法

时间:2013-04-09 15:09:30

标签: javascript internet-explorer memory-leaks

在我们的Javascript库测试期间,我认为我们在IE10(v10.0.9200.16519 - Windows 8 64位)setInterval的Javascript实现中发现了严重的内存泄漏。

一个简单的测试用例表明,如果在作为参数传递的函数的闭包中捕获变量以供稍后执行,那么它似乎没有资格进行垃圾收集,即浏览器似乎仍然保留了对函数或至少是闭包变量。

我们的测试用例只执行一次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,并且该对象永远不会被垃圾回收。)

我们的代码中是否存在错误,或者是否有办法使用setInterval,其中闭包变量保存对大对象的引用,而不会触发内存泄漏而不会恢复为“递归”setTimeout呼叫?

(我也是posted the question on MSDN

更新:此问题也存在于Windows 7的IE10中,但如果切换到IE9标准模式则不存在。我将此提交给MS Connect并将报告进度。

更新:Microsoft accepted the issue并将其报告为在IE11中修复(预览版) - 我自己尚未确认,但(任何人?)

更新: IE 11已正式发布,我无法再使用我的系统(Win 8.1 Pro 64bit)重现该版本的问题。

1 个答案:

答案 0 :(得分:7)

为了完整起见,我在这里添加了一个可能的解决方法:

正如我已经写过的(和评论者的建议),这可以通过回到setTimeout来解决(不固定)。这不是微不足道的,因为需要进行一些id簿记。以下是我建议的修复方法,您可以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执行簿记,所以除非需要,否则我不建议应用此修复。

不幸的是我无法想出一个“特征”检测算法(更像是“bug” - 检测算法),所以我猜你必须恢复到良好的旧浏览器检测。此外,我的实现不能将字符串作为第一个参数处理,也不会将其他参数传递给内部函数。最后,将此方法调用两次是不安全的,因此使用它需要您自担风险(并随时改进)!

(注意:对于我们的库,我们将从现在开始停止使用setInterval,而是重写代码中依赖它的几个部分直接使用setTimeout。)