在我们的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)重现该版本的问题。
答案 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
。)