使用 node.js 进行垃圾收集

IT技术 javascript node.js v8
2021-02-03 20:18:37

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

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

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

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

3个回答

简单的答案:如果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,并z只有当将消失两个 inner1inner2 inner3死。发生这种情况是因为yz分配在相同的上下文结构中,并且所有三个闭包都隐式地引用了这个上下文结构(即使inner3没有显式使用它)。

当您开始使用with -statement、try/catch -statement 时,情况会变得更加复杂它们在 V8 上在 catch 子句或 global 中包含一个隐含的with -statement 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子句语句来
  • V8 假设 any with -statement 会影响所有本地人

这会强制x成为上下文变量并inner捕获上下文,x直到inner死亡为止

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

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

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

它将str在调用 Server.start() 后超出范围并最终被收集。此外,它还将使您的缩进更易于管理,对于更复杂的程序来说,这一点不容小觑。

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

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

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

我的猜测是 str 不会被垃圾收集,因为它可以被 restofprogram() 使用。是的,如果在外部声明了 restofprogram,则 str 应该被 GCed,除非您执行以下操作:

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实际使用(或可以令人信服地与使用eval中的身体语句)restofprogram它是否这样做是应该向了解 v8 细节的人提出的问题。
2021-03-25 20:18:37
V8 使用分代垃圾收集器。
2021-03-30 20:18:37
@dhruvbird 如果函数体中evalornew Function语句,则str可以想象使用,因此不会被 GC 处理。如果没有,并且str在函数体中不存在直接引用,那么它可能会被 GC 处理。实际上很简单,但是否有效利用处理器时间是另一个问题......
2021-04-03 20:18:37
@MooGoo 我怀疑任何 GC 是否足够智能以检测在 eval 中使用的“str”(因为要评估的字符串可以从用户输入中获得)
2021-04-05 20:18:37
@MooGoo eval 的规则非常复杂,所以我不记得它们了,但我想外部实体有可能通过作用域或参数将句柄传递给函数内部的 eval,然后您可以在内部进行 eval功能。(我不确定用其他别名调用的 eval 的范围规则,所以不要在这方面引用我的话)。
2021-04-10 20:18:37