【问题标题】:How does setTimeout() create a memory leak in this code?setTimeout() 如何在这段代码中造成内存泄漏?
【发布时间】:2013-08-07 22:32:59
【问题描述】:

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

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

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

buggyObject.callAgain();
buggyObject = null;

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

【问题讨论】:

  • 显然,将永远引用buggyObject,但我不确定您是否会称其为传统意义上的内存泄漏。它们可能暗示某种循环,因为闭包引用了超时,所以理论上每个调用都会创建一个新的长寿命闭包,除了 val 是句柄而不是引用。
  • 这也让我很困惑。我觉得我错过了一个重要的点,我不确定我是否是。我想他们唯一的一点是非常明显的一点,即您将无限期地运行一段代码,并且它将使用足够的内存来运行它(并在该过程中保持原始的 buggyObject 存活)。但这似乎非常明显。我认为他们试图就将​​产生的呈指数级增长的泄漏提出一些微妙的观点,但我不知道如何。
  • 有人能给出这个代码的固定版本吗?

标签: javascript memory-leaks


【解决方案1】:

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

  • 我将整个代码放在一个循环中,以创建相同的场景 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;
}

我的实验:

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

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

是什么原因造成的?

这种行为的主要原因是计时器。定时器回调和它的绑定对象,buggyObject 在超时发生之前不会被释放。在我们的例子中,计时器会自行重置并永远运行,因此即使没有对原始对象的引用,它的内存空间也永远不会被收集。

【讨论】:

  • 这里描述的内容在我看来并不像内存泄漏。每运行 100000 个计时器只有 32MB。内存没有增加!
  • 如果你继续运行代码,它会增加并且永远不会被垃圾回收。
  • 在任何非浏览器应用程序中,这是一个巨大的问题!
【解决方案2】:

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

但是,我认为作者想说的是,由于 buggyObject 创建了一个调用自身的 setTimeout,即使您将 buggyObject 更改为等于 null(说您已完成该对象并且可以清理up),该对象不会被垃圾回收,因为在 setTimeout() 中仍然存在对它的引用。这在技术上是内存泄漏,因为不再有任何对 setTimeout 函数的直接引用,因此您可以稍后将其清除(如果您愿意的话,有点像僵尸超时)。

【讨论】:

    【解决方案3】:

    正如 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() 函数来做您需要做的事情:一遍又一遍地调用相同的函数。现在您仍然有“内存泄漏”,但只有初始化参数泄漏(即每次计时器超时时它都不会增长。)

    所以从概念上讲,调用是平坦的,您可以避免泄漏:

    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...
    

    【讨论】:

      猜你喜欢
      • 2019-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多