使用 Node.js 进行垃圾收集

发布于 2024-10-22 14:55:57 字数 383 浏览 1 评论 0 原文

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

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

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

编辑我并不想让问题变得复杂。这只是粗心,所以我修改了它。

I was curious about how the node.js pattern of nested functions works with the garbage collector of v8.
here's a simple example

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

if restofprogram is long-running, doesn't that mean that str will never get garbage collected? My understanding is that with node you end up with nested functions a lot. Does this get garbage collected if restofprogram was declared outside, so str could not be in scope? Is this a recommended practice?

EDIT I didn't intend to make the problem complicated. That was just carelessness, so I've modified it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

也只是曾经 2024-10-29 14:55:57

简单的答案:如果 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];
}

在这个示例中,一旦 outer 返回,变量 x 就会消失,但变量 yz仅当两者inner1inner2inner3死亡时才会消失。发生这种情况是因为 yz 分配在相同的上下文结构中,并且所有三个闭包隐式引用此上下文结构(甚至不使用的 inner3明确地)。

当您开始使用 with 语句、try/catch 语句时,情况会变得更加复杂,在 V8 上,该语句在 catch 中包含隐式 <​​strong>with 语句子句或全局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;
}

在此示例中,仅当 inner 死亡时,x 才会消失。因为:

  • try/catch - catch 子句中包含隐式 <​​strong>with 语句
  • V8 假定任何 with 语句都会影响全部 局部变量

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

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

Simple answer: if value of the str is not referenced from anywhere else (and str itself is not referenced from restofprogram) it will become unreachable as soon as the function (str) { ... } returns.

Details: V8 compiler distinguishes real local variables from so called context variables captured by a closure, shadowed by a with-statement or an eval invocation.

Local variables live on the stack and disappear as soon as function execution completes.

Context variables live in a heap allocated context structure. They disappear when the context structure dies. Important thing to note here is that context variables from the same scope live in the same structure. Let me illustrate it with an example code:

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

In this example variable x will disappear as soon as outer returns but variables y and z will disappear only when both inner1, inner2 and inner3 die. This happens because y and z are allocated in the same context structure and all three closures implicitly reference this context structure (even inner3 which does not use it explicitly).

Situation gets even more complicated when you start using with-statement, try/catch-statement which on V8 contains an implicit with-statement inside catch clause or 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;
}

In this example x will disappear only when inner dies. Because:

  • try/catch-contains implicit with-statement in catch clause
  • V8 assumes that any with-statement shadows all the locals

This forces x to become a context variable and inner captures the context so x exists until inner dies.

In general if you want to be sure that given variable does not retain some object for longer than really needed you can easily destroy this link by assigning null to that variable.

甜柠檬 2024-10-29 14:55:57

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

我的猜测是,在restofprogram()函数完成之前,str变量不会被收集,即使它不使用它。 如果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 函数共同的外部作用域中似乎是这样最直接的解决方案。

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

Actually your example is somewhat tricky. Was it on purpose? You seem to be masking the outer val variable with an inner lexically scoped restofprogram()'s val argument, instead of actually using it. But anyway, you're asking about str so let me ignore the trickiness of val in your example just for the sake of simplicity.

My guess would be that the str variable won't get collected before the restofprogram() function finishes, even if it doesn't use it. If the restofprogram() doesn't use str and it doesn't use eval() and new Function() then it could be safely collected but I doubt it would. This would be a tricky optimization for V8 probably not worth the trouble. If there was no eval and new Function() in the language then it would be much easier.

Now, it doesn't have to mean that it would never get collected because any event handler in a single-threaded event loop should finish almost instantly. Otherwise your whole process would be blocked and you'd have bigger problems than one useless variable in memory.

Now I wonder if you didn't mean something else than what you actually wrote in your example. The whole program in Node is just like in the browser – it just registers event callbacks that are fired asynchronously later after the main program body has already finished. Also none of the handlers are blocking so no function is actually taking any noticeable time to finish. I'm not sure if I understood what you actually meant in your question but I hope that what I've written will be helpful to understand how it all works.

Update:

After reading more info in the comments on how your program looks like I can say more.

If your program is something like:

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

Then you can also write it like this:

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

It will make the str go out of scope after Server.start() is called and will eventually get collected. Also, it will make your indentation more manageable which is not to be underestimated for more complex programs.

As for the val you might make it a global variable in this case which would greatly simplify your code. Of course you don't have to, you can wrestle with closures, but in this case making val global or making it live in an outer scope common for both the readfile callback and for the serverCallback function seems like the most straightforward solution.

Remember that everywhere when you can use an anonymous function you can also use a named function, and with those you can choose in which scope do you want them to live.

潇烟暮雨 2024-10-29 14:55:57

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

My guess is that str will NOT be garbage collected because it can be used by restofprogram().
Yes, and str should get GCed if restofprogram was declared outside, except, if you do something like this:

function restofprogram(val) { ... }

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

Or if getvaluefromstr is declared as something like this:

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

Follow-up-question: Does v8 do just plain'ol GC or does it do a combination of GC and ref. counting (like python?)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文