通常,在带有关键字'var‘的函数中定义的Javascript变量应该是函数范围的。但我对一个与node.js子进程相关的情况感到困惑。我有两个文件,app.js (它是主文件)和child.js (它是子进程的执行脚本):
// app.js
const { fork } = require('child_process');
function bar() {
var child = fork(`${__dirname}/child.js`);
child.on('message', msg => {
console.log("fib(46) = " + msg);
});
}
bar();
gc(); // force the garbage collection// child.js是计算密集型任务,需要几十秒才能完成
function fib(n) {
return n < 2 ? 1 : fib(n - 1) + fib(n - 2);
}
var result = fib(46);
process.send(JSON.stringify(result));我原以为这段代码可能会抛出一些异常,因为变量“子”是在函数bar()中创建的本地对象。当函数bar()完成执行时,它应该是垃圾收集的。注册用于接收子进程消息的事件处理程序只是保存在“子”对象的哈希表中(子对象也是基于node.js子进程模块的文档的node.js实例),因此这些哈希表也应该作为本地“子”对象被回收时被垃圾收集。
但实际情况是,几十秒后,当子进程完成计算fib(46)并通过process.send将结果发回时,父进程成功地发出事件,并最终得到已注册的事件处理程序:
$ node -公开-gc .\app.js
fib(46) = 2971215073
那么,看起来子进程对象(局部变量“子”指向的对象)还在内存中吗?或者子进程对象是由node.js子进程模块在内部引用的,所以不能在子进程结束之前进行垃圾收集?我真的需要有人帮我澄清情况。
发布于 2021-03-06 04:44:45
只有当没有其他代码可以到达、使用或引用该变量时,垃圾收集才能收集并回收其内存。有时,当函数声明一个局部变量,然后函数执行并返回时,就会发生这种情况,但并不总是这样。
如果有异步操作正在运行(并且您的子进程是异步操作),并且在该函数范围内声明了事件处理程序或回调,并且仍然可以调用这些回调/事件,那么该函数作用域中的可访问变量将无法被垃圾收集,直到它们真正不可到达,在您的情况下,这些变量可能在函数已经执行和返回之后很长很长一段时间内被执行和返回,因为该生存期与子进程的生存期相关联,而不是与函数执行的生存期相关联。
在您的特定情况下,因为child对象仍然可以在函数返回后很长时间内发出事件,那么该child对象及其事件处理程序可能访问的其他任何东西都不能被垃圾收集,直到子对象本身是GCed,或者直到其他情况告诉垃圾收集器不能再调用该事件处理程序。
因此,请记住,在Javascript中,函数中的局部变量不会进入一个严格的堆栈框架,即在函数返回时立即100%地回收。相反,它们是作用域对象的属性,而该作用域对象上的每个单独属性只有在任何活动代码无法再访问时才能被垃圾收集。因此,scope对象中的某些内容有可能被垃圾收集,因为它们在任何仍然可以调用的代码中没有引用,而范围对象上的其他东西还不能被垃圾收集,因为在该函数范围内声明了回调,这些回调可能仍然引用在该函数中声明的一些变量。同样的规则适用于函数作用域或块作用域变量,因为函数作用域只是一个比块作用域更大的块--它们现在的工作原理基本相同。
下面是一个非常简单的例子:
function run() {
let total = 0;
let msg = "About to start timer";
console.log(msg);
const timer = setInterval(() => {
++total;
if (total >= 50) {
console.log(total);
clearInterval(timer);
}
}, 50);
}
run();在这段代码中,一旦函数msg退出,局部变量run()就可用于GC。
在调用total并完成最后的计时器回调之后,clearInterval()变量才能用于GC。
https://stackoverflow.com/questions/66501911
复制相似问题