JavaScript 闭包是如何被垃圾回收的

问题描述

我在 IE9+ 和 Firefox 中对此进行了测试。

function f() {
  var some = [];
  while(some.length < 1e6) {
    some.push(some.length);
  }
  function g() { some; } //removing this fixes a massive memory leak
  return function() {};   //or removing this
}

var a = [];
var interval = setInterval(function() {
  var len = a.push(f());
  if(len >= 500) {
    clearInterval(interval);
  }
}, 10);

现场直播

我希望以 500 的数组结束function() {},使用最少的内存。

不幸的是,事实并非如此。每个空函数都持有一个(永远无法访问,但不是 GC’ed)一百万个数字的数组。

Chrome 最终停止并死掉,Firefox 在使用了近 4GB 的 RAM 后完成了整个过程,而 IE 逐渐变慢,直到它显示“内存不足”。

删除任一注释行可以解决所有问题。

似乎所有这三种浏览器(Chrome、Firefox 和 IE)都为每个上下文而不是每个闭包保留了环境记录。Boris 假设这个决定背后的原因是性能,这似乎很可能,尽管我不确定根据上述实验可以调用它的性能如何。

如果需要一个闭包引用some(当然我在这里没有使用它,但想象一下我做了),如果不是

function g() { some; }

我用

var g = (function(some) { return function() { some; }; )(some);

它将通过将闭包移动到与我的其他函数不同的上下文来解决内存问题。

这会让我的生活更加乏味。

PS出于好奇,我在Java中尝试了这个(利用它在函数内部定义类的能力)。GC 就像我最初希望的 Javascript 一样工作。

解决方法

我记录了以下Chrome 错误,这导致我的代码中有许多严重且不明显的内存泄漏:

(这些结果使用 Chrome Dev Tools 的内存分析器,它运行 GC,然后对所有没有被垃圾收集的东西进行堆快照。)

在下面的代码中,someClass实例被垃圾收集(好):

var someClass = function() {};

function f() {
  var some = new someClass();
  return function() {};
}

window.f_ = f();

但在这种情况下不会被垃圾收集(坏):

var someClass = function() {};

function f() {
  var some = new someClass();
  function unreachable() { some; }
  return function() {};
}

window.f_ = f();

以及相应的截图:

Chromebug 的屏幕截图

如果对象被同一上下文中的任何其他闭包引用,则闭包(在本例中为 )似乎function() {}使所有对象保持“活动”,无论该闭包本身是否可访问。

我的问题是关于其他浏览器(IE 9+ 和 Firefox)中闭包的垃圾收集。我对 webkit 的工具非常熟悉,比如 JavaScript
堆分析器,但是我对其他浏览器的工具知之甚少,所以我一直无法对此进行测试。

在这三种情况下,IE9+ 和 Firefox 会垃圾收集 someClass 实例中的哪一种?