setTimeout()如何在此代码中创建内存泄漏?

时间:2013-08-06 18:56:52

标签: javascript memory-leaks

我正在审核此演示文稿中的幻灯片:http://slid.es/gruizdevilla/memory

并在其中一张幻灯片上显示此代码,建议它会造成内存泄漏:

var buggyObject = {
   callAgain: function() {
     var ref = this;
     var val = setTimeout(function() {
        ref.callAgain();
     }, 1000);
   }
}

buggyObject.callAgain();
buggyObject = null;

有人可以在这里更详细地解释这个问题吗?我可能会在这里遗漏一些细微之处。

3 个答案:

答案 0 :(得分:6)

这绝对是内存泄漏。但是,内存消耗很小,无法测量。我对源代码做了一些小改动。

  • 我将整个代码放在循环中以创建相同的方案100,000次
  • 我将计时器间隔增加到大约16分钟。这可以防止浏览器崩溃

以下是代码:

for (var i = 0; i < 100000; i++) {
    var buggyObject = {
       callAgain: function() {
         var ref = this;
         var val = setTimeout(function() {
            ref.callAgain(); 
         }, 1000000); //16m
       }
    }

    buggyObject.callAgain();
    buggyObject = null;
}

Memory leak in Chrome

我的实验:

我在 Chrome版本34.0.1847.116 m 中运行了代码,并使用Developer Tools \ Timeline捕获了内存更改。

正如我们在图片中看到的,运行此代码消耗了大约32 MB的内存,一段时间后它已经减少到大约30 MB并保持不变(参见#1)。 经过Chrome的几次垃圾收集尝试(参见#2)和我的一次手动强制垃圾收集(参见#3,#4),内存消耗保持不变。 没有buggyObject,我们无法释放内存。唯一可行的方法是关闭浏览器。

是什么导致这种情况?

此行为的主要原因是计时器。定时器回调及其绑定对象,buggyObject在超时发生之前不会被重新启动。在我们的情况下,计时器会自行重置并永久运行,因此即使没有对原始对象的引用,也不会收集其内存空间。

答案 1 :(得分:5)

another question描述了setTimeout()看起来有内存泄漏的方式,但实际上并没有。

但是,我认为作者试图说的是,因为buggyObject创建了一个调用自身的setTimeout,即使你将buggyObject更改为等于null(说你完成了对象和它可以被清理),对象不会被垃圾收集,因为在setTimeout()中仍然有对它的引用。这在技术上是一个内存泄漏,因为不再有任何对setTimeout函数的直接引用,以便您以后可以清除它(如果你愿意,可以使用zombie超时)。

答案 2 :(得分:0)

正如advncd指出的那样,计时器被执行并在堆栈上添加更多数据。概述了发生了什么:

var a = 123;
// call the setTimeout.function
  var a = 123;
  // call the setTimeout.function
    var a = 123;
    // call the setTimeout.function
      var a = 123;
      // call the setTimeout.function
        var a = 123;
        // call the setTimeout.function
          var a = 123;
          // call the setTimeout.function
            var a = 123;
            // call the setTimeout.function
              var a = 123;
              // call the setTimeout.function
                var a = 123;
                ...etc...

因此每次在堆栈上分配一个新的变量a时,它会永远增长。

然而,advncd没有提到的事实是你有一个setInterval()函数来做你需要做的事情:一遍又一遍地调用相同的函数。现在你仍然有一个&#34;内存泄漏&#34;但只有初始化参数泄漏(即每次定时器超时,它都不会增长。)

从概念上讲,这些电话是平的,你可以避免泄密:

a = 123;
// call the setTimeout.function
// call the setTimeout.function
// call the setTimeout.function
// call the setTimeout.function
// call the setTimeout.function
// call the setTimeout.function
// call the setTimeout.function
...etc...