11

在测试我们的 Javascript 库期间,我认为我们在setInterval.

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

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

有趣的是,如果我们改用该方法,则不会发生这种情况setTimeout(而且在 IE9 和当前版本的 Chrome、FF 中似乎不存在该问题)

这个 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调用?

(我也在MSDN 上发布了这个问题

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

更新: Microsoft接受了该问题并报告它已在 IE11(预览版)中修复 - 我自己还没有确认这一点,但(有人吗?)

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

4

1 回答 1

7

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

正如我已经写过的(和评论者建议的),这可以通过回退到setTimeout. 这不是微不足道的,因为需要进行一些 id 簿记。这是我建议的修复方法,您可以从这个小提琴中测试和分叉

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直接使用的少数部分。)

于 2013-04-10T07:22:42.480 回答