你经常在网上看到使用闭包是JavaScript中内存泄漏的一个巨大来源。大多数情况下,这些文章指的是混合脚本代码和DOM事件,其中脚本指向DOM,反之亦然。
我知道,在那种情况下,闭包可能会成为问题。
但是Node.js呢?在这里,我们自然没有DOM - 所以不存在像浏览器中那样有内存泄漏的副作用。
闭包还可能存在哪些问题?有人能详细说明或指点我一个好的教程吗?
请注意,此问题明确针对Node.js,而不是浏览器。
你经常在网上看到使用闭包是JavaScript中内存泄漏的一个巨大来源。大多数情况下,这些文章指的是混合脚本代码和DOM事件,其中脚本指向DOM,反之亦然。
我知道,在那种情况下,闭包可能会成为问题。
但是Node.js呢?在这里,我们自然没有DOM - 所以不存在像浏览器中那样有内存泄漏的副作用。
闭包还可能存在哪些问题?有人能详细说明或指点我一个好的教程吗?
请注意,此问题明确针对Node.js,而不是浏览器。
这个问题类似。基本上,如果在回调中使用闭包,当你完成后应该“取消订阅”回调,这样GC就知道不能再次调用它。我觉得这很有道理;如果你有一个闭包在等待被调用,GC将难以知道你已经完成了它。通过手动从回调机制中删除闭包,它就变得未引用且可供收集。
此外,Mozilla发表了一篇关于发现Node.js代码中内存泄漏的好文章。我认为,如果你尝试他们的一些策略,你可以找出表现出漏气行为的代码部分。最佳实践很好,但我认为更有帮助的是了解程序的需求,并根据你可以经验性地观察到的情况提出一些个性化的最佳实践。
以下是来自Mozilla文章的快速摘录:
- Jimb Esser的
node-mtrace
,使用GCC的mtrace
工具对堆使用情况进行分析。- Dave Pacheco的
node-heap-dump
对V8堆进行快照,并将整个内容序列化到一个巨大的JSON文件中。它包括用于在JavaScript中遍历和调查结果快照的工具。- Danny Coates的
v8-profiler
和node-inspector
为V8分析器提供了Node绑定和使用WebKit Web Inspector的Node调试接口。- Felix Gnass的同样的分叉,取消了保留者图形的禁用。
- Felix Geisendörfer的《Node Memory Leak Tutorial》是一个简短而精炼的解释如何使用
v8-profiler
和node-debugger
,并且现在对于大多数Node.js内存泄漏调试来说是最先进的。- Joyent的SmartOS平台,为调试Node.js内存泄漏提供了一系列有用的工具。
这个问题的答案基本上都说你可以通过将闭包变量赋值为null
来帮助GC。
var closureVar = {};
doWork(function callback() {
var data = closureVar.usefulData;
// Do a bunch of work
closureVar = null;
});
在函数内声明的任何变量都会在函数返回时消失,除非它们被用于其他闭包中。在这个例子中,closureVar
必须一直保留在内存中直到调用 callback()
函数,但是谁知道那会发生在什么时候呢?一旦回调被调用,你可以通过将闭包变量设置为 null 来向 GC 提供提示。
免责声明:正如您可以从下面的评论中看到的那样,有些 SO 用户说这些信息已经过时,在 Node.js 中没有实际意义。我还没有一个明确的答案; 我只是发布了我在网上找到的内容。
你可以在David Glasser的博客文章中找到一个好的例子和解释。
好的,这就是它(我添加了一些注释):
var theThing = null;
var cnt = 0; // helps us to differentiate the leaked objects in the debugger
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing) // originalThing is used in the closure and hence ends up in the lexical environment shared by all closures in that scope
console.log("hi");
};
// originalThing = null; // <- nulling originalThing here tells V8 gc to collect it
theThing = {
longStr: (++cnt) + '_' + (new Array(1000000).join('*')),
someMethod: function () { // if not nulled, original thing is now attached to someMethod -> <function scope> -> Closure
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
请在Chrome Dev Tools(时间轴标签,内存视图,单击记录)中尝试使用和不使用originalThing
进行空值处理。请注意,上面的示例适用于浏览器和Node.js环境。
特别鸣谢Vyacheslav Egorov。
--expose-gc
并定期调用 gc()
是不好的实践。V8 通过增量标记和惰性清除根据需要收集垃圾。处理上述行为的适当方法是将指向已成为词法环境一部分的对象的本地变量置空。 - borisdiakur