【问题标题】:garbage collection with node.js使用 node.js 进行垃圾收集
【发布时间】:2011-07-16 15:02:18
【问题描述】:

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

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

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

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

【问题讨论】:

标签: javascript node.js v8


【解决方案1】:

实际上,您的示例有些棘手。是故意的吗?您似乎使用内部词法范围的 restofprogram() 的 val 参数屏蔽外部 val 变量,而不是实际使用它。但无论如何,您是在询问str,所以为了简单起见,让我忽略您示例中val 的棘手之处。

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

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

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

更新:

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

如果你的程序是这样的:

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
});

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

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

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

【讨论】:

  • 是的,但是如果 restofprogram 类似于 Server.start(function(request) {do something}),即使 restofprogram 立即退出,传递给 Server.start 的函数将永远存在,并且具有 str在范围内。
  • 实际上,事件处理程序可以创建一个匿名函数,作为事件侦听器添加到某个其他事件,并且每次调用它时都可以这样做,从而确保所有范围变量(对于所有此处理程序的调用)永远不会被收集。
  • @dhruvbird:是的。对于这些情况,我建议使用命名函数,您可以选择它们所在的范围。
  • @Vishnu:请参阅我的答案的更新,了解有关如何使此类案例更易于管理的一些想法。
  • 谢谢,这就是我的问题的意图。因此,可能会出现意外的内存泄漏,并且尽可能使用命名函数应该可以缓解问题。
【解决方案2】:

我的猜测是 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?)

【讨论】:

  • 从技术上讲,如果 v8 GC 足够智能,它应该确定 str 是否在 restofprogram 的主体中实际使用(或者可以与 eval 语句一起使用)。是否这样做是应该向了解 v8 细节的人提出的问题。
  • V8 使用分代垃圾收集器。
  • @MooGoo 我怀疑任何 GC 都足够聪明,可以检测到 eval 中使用的“str”(因为要评估的字符串可以从用户输入中获得)
  • @dhruvbird 如果函数体中有evalnew Function 语句,那么str 可以被使用,因此不会被GC'd。如果不是,并且函数体中不存在对str 的直接引用,那么它可能会被GC'd。实际上很简单,但是否有效利用处理器时间是另一个问题......
  • @MooGoo eval 的规则非常复杂,所以我记不太清了,但我猜外部实体可以通过范围将句柄传递给函数内部的 eval或参数,然后您可以在函数内进行评估。 (我不确定用其他别名调用的 eval 的范围规则,所以不要引用我的话)。
【解决方案3】:

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

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

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

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

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 返回时消失,但变量 yz 仅在 两者 inner1, inner2 时才会消失和 inner3 死。这是因为yz 分配在同一个上下文结构中,并且所有三个闭包都隐式引用了这个上下文结构(即使inner3 没有显式使用它)。

当你开始使用 with-statement 时,情况变得更加复杂,try/catch-statement 在 V8 上包含一个隐含的 with-在 catch 子句或全局 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 假定任何 with 语句都会影响 所有 本地人

这会强制 x 成为上下文变量,inner 捕获上下文,因此 x 一直存在,直到 inner 死亡。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-16
    • 2016-01-29
    • 1970-01-01
    • 1970-01-01
    • 2011-03-10
    • 2019-09-04
    • 1970-01-01
    相关资源
    最近更新 更多