【问题标题】:Workaround for IE10 setInterval Memory LeakIE10 setInterval 内存泄漏的解决方法
【发布时间】:2013-04-01 02:27:13
【问题描述】:

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

一个简单的测试用例表明,如果一个变量在作为参数传递的函数的闭包中被捕获以供以后执行,它似乎永远不适合垃圾收集,即浏览器似乎仍然持有对函数或至少是闭包变量。

我们的测试用例只执行一次setInterval 函数,然后清除间隔计时器,即一段时间后不再运行代码并且不再可访问变量(据我所知,此代码中没有引入全局变量,除了在onload 中运行的方法之外,该过程仍然占用了半 GB 的内存(取决于迭代次数)。

有趣的是,如果我们改用 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)在那个版本上重现问题。

【问题讨论】:

  • 为什么要创建一个间隔然后立即清除它?这不是违背了目的吗?
  • @BradM 我用解释更新了这个问题 - 真正的代码比这更复杂,并且并不总是无条件地清除间隔。
  • 您是否尝试过不使用匿名函数,而是使用函数引用,然后显式覆盖此引用?
  • “如果我们使用 setTimeout 方法,这不会发生” - 那么,问题解决了吗?使用 setInterval 可以做的任何事情都可以通过重复的 setTimeout 调用来做,对吧?
  • @MarkBessey 是的,也不是 :-) 我的代码可以正常工作,是的,但实际问题(泄漏)没有解决。当然,我可以重写setInterval 实现——当然,我宁愿理解我的代码中的问题(如果有的话)或者在 IE 中修复它。这个问题得到的关注越多,修复的可能性就越大,未来受影响的人就越少。所以我讨厌称这个问题为“已解决”,尽管现在看起来,我会这样做。

标签: javascript internet-explorer memory-leaks


【解决方案1】:

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

正如我已经写过的(和评论者建议的),这可以通过回退到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调用。这个实现有一点开销,因为它必须为不断变化的ids 执行簿记,所以除非需要,否则我不建议应用此修复程序。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-14
    • 1970-01-01
    • 2012-12-11
    • 1970-01-01
    相关资源
    最近更新 更多