2013-04-09 131 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代码重写几部分组成。)