29

我很好奇嵌套函数的 node.js 模式如何与 v8 的垃圾收集器一起工作。这是一个简单的例子

readfile("blah", function(str) {
   var val = getvaluefromstr(str);
   function restofprogram(val2) { ... } (val)
})

如果 restofprogram 长时间运行,这是否意味着 str 永远不会被垃圾收集?我的理解是,使用 node 你最终会得到很多嵌套函数。如果在外部声明了 restofprogram 是否会被垃圾收集,所以 str 不能在范围内?这是推荐的做法吗?

编辑我不打算使问题复杂化。那只是粗心,所以我已经修改了它。

4

3 回答 3

70

简单的回答:如果str没有从其他任何地方引用 的值(并且str它本身也没有从 引用restofprogram),那么一旦function (str) { ... }返回,它将变得无法访问。

详细信息:V8 编译器将真正的局部变量与闭包捕获的所谓上下文变量区分开来,由with语句或eval调用遮蔽。

局部变量存在于堆栈中,并在函数执行完成后立即消失。

上下文变量存在于堆分配的上下文结构中。当上下文结构消失时,它们就会消失。这里要注意的重要一点是来自同一范围的上下文变量存在于相同的结构中。让我用一个示例代码来说明它:

function outer () {
  var x; // real local variable
  var y; // context variable, referenced by inner1
  var z; // context variable, referenced by inner2

  function inner1 () {
    // references context 
    use(y);
  }

  function inner2 () {
    // references context 
    use(z);
  }

  function inner3 () { /* I am empty but I still capture context implicitly */ } 

  return [inner1, inner2, inner3];
}

在此示例中,变量x将在outer返回时消失,但变量只有在两者y都消失时才会消失,并且死亡。发生这种情况是因为and被分配在同一个上下文结构中,并且所有三个闭包都隐式引用了这个上下文结构(即使它没有显式使用它)。z inner1inner2 inner3yzinner3

当您开始使用with -statement时,情况会变得更加复杂, try/catch -statement 在 V8 上包含一个隐式with -statement inside catch 子句或 global eval

function complication () {
  var x; // context variable

  function inner () { /* I am empty but I still capture context implicitly */ }

  try { } catch (e) { /* contains implicit with-statement */ }

  return inner;
}

在这个例子x中,只有在死亡时才会消失inner。因为:

  • try/catch - 在 catch 子句中包含隐含的 with - 语句
  • V8 假定任何带有-statement的内容都会影响所有本地人

这迫使x成为一个上下文变量并inner捕获上下文,因此x存在直到inner死亡。

通常,如果您想确保给定变量不会保留某个对象的时间超过实际需要的时间,您可以通过分配给该变量来轻松破坏此链接。null

于 2011-03-16T16:53:03.583 回答
4

其实你的例子有点棘手。是故意的吗?您似乎用内部词法范围的 restofprogram()参数掩盖了外部变量,而不是实际使用它。但是无论如何,您要问的是,为了简单起见,让我忽略您示例中的棘手之处。valvalstrval

我的猜测是str在 restofprogram() 函数完成之前不会收集变量,即使它不使用它。如果restofprogram() 不使用str 并且它不使用eval()new Function()那么它可以被安全地收集,但我怀疑它会。这对于 V8 来说是一个棘手的优化,可能不值得麻烦。如果没有evalnew Function()语言,那么它会容易得多。

现在,这并不一定意味着它永远不会被收集,因为单线程事件循环中的任何事件处理程序都应该几乎立即完成。否则你的整个过程会被阻塞,你会遇到比内存中一个无用的变量更大的问题。

现在,我想知道您的意思是否与您在示例中实际写的内容不同。Node 中的整个程序就像在浏览器中一样——它只是注册事件回调,稍后在主程序主体完成后异步触发。此外,没有任何处理程序被阻塞,因此实际上没有任何功能需要任何明显的时间来完成。我不确定我是否理解您在问题中的实际意思,但我希望我所写的内容有助于理解这一切是如何运作的。

更新:

在阅读有关您的程序外观的评论中的更多信息后,我可以说更多。

如果你的程序是这样的:

readfile("blah", function (str) {
  var val = getvaluefromstr(str);
  // do something with val
  Server.start(function (request) {
    // do something
  });
});

然后你也可以这样写:

readfile("blah", function (str) {
  var val = getvaluefromstr(str);
  // do something with val
  Server.start(serverCallback);
});
function serverCallback(request) {
  // do something
});

在调用 Server.start() 后,它将str超出范围并最终被收集。此外,它将使您的缩进更易于管理,这对于更复杂的程序是不可低估的。

至于val在这种情况下,您可以将其设为全局变量,这将大大简化您的代码。当然,您不必这样做,您可以与闭包搏斗,但在这种情况下,将val其设为全局或使其存在于 readfile 回调和 serverCallback 函数通用的外部范围中似乎是最直接的解决方案。

请记住,当您可以使用匿名函数时,您也可以使用命名函数,并且您可以选择希望它们存在于哪个范围内。

于 2011-03-16T14:38:51.193 回答
1

我的猜测是 str 不会被垃圾收集,因为它可以被 restofprogram() 使用。是的,如果在外面声明了 restofprogram,str 应该被 GC,除非你做这样的事情:

function restofprogram(val) { ... }

readfile("blah", function(str) {
  var val = getvaluefromstr(str);
  restofprogram(val, str);
});

或者,如果 getvaluefromstr 被声明为如下所示:

function getvaluefromstr(str) {
  return {
    orig: str, 
    some_funky_stuff: 23
  };
}

后续问题:v8 只是做普通的 GC 还是做 GC 和 ref 的组合。计数(像python?)

于 2011-03-16T14:02:16.523 回答