JavaScript中的内存泄漏和关闭 - 何时以及为何?

时间:2013-10-23 19:04:16

标签: node.js memory-leaks closures

您经常在网上看到,使用闭包是JavaScript中的大量内存泄漏源。这些文章大多数时候都是指混合脚本代码和DOM事件,其中脚本指向DOM,反之亦然。

我知道闭包可能是个问题。

但是Node.js怎么样?在这里,我们自然没有DOM - 所以没有机会像浏览器一样有内存泄漏副作用。

关闭可能还有哪些其他问题?任何人都可以详细说明或指出一个很好的教程吗?

请注意,此问题明确针对Node.js,而不是浏览器。

3 个答案:

答案 0 :(得分:36)

This question询问类似的事情。基本上,我们的想法是,如果在回调中使用闭包,则应在完成后“取消订阅”回调,以便GC知道无法再次调用它。这对我来说很有意义;如果你有一个关闭只是等待被调用,那么GC将很难知道你已经完成了它。通过从回调机制中手动删除闭包,它将被取消引用并可供收集。

此外,Mozilla已发布a great article on finding memory leaks in Node.js代码。我假设如果你尝试他们的一些策略,你可以找到代表泄漏行为的部分代码。最好的做法很好,但我认为理解你的程序需求更有帮助,并根据你的经验观察得出一些个性化的最佳实践。

以下是Mozilla文章的快速摘录:

  
      
  • Jimb Esser的node-mtrace,它使用GCC mtrace实用程序来分析堆使用情况。
  •   
  • Dave Pacheco的node-heap-dump拍摄了V8堆的快照,并将整个事件序列化为一个巨大的JSON文件。它包括用JavaScript遍历和调查生成的快照的工具。
  •   
  • Danny Coates的v8-profilernode-inspector使用WebKit Web Inspector为V8探查器和Node调试接口提供节点绑定。
  •   
  • 菲利克斯·格纳斯(Felix Gnass)同样禁用“保留者图”的分叉
  •   
  • FelixGeisendörfer的节点内存泄漏教程是关于如何使用v8-profilernode-debugger的简短而精彩的解释,目前是大多数Node.js内存泄漏的最新技术调试。
  •   
  • Joyent的SmartOS平台,为您调试Node.js内存泄漏提供了大量工具
  •   

this question的答案基本上说你可以通过将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无关紧要。我还没有明确的答案;我只是发布了我在网上发现的内容。

答案 1 :(得分:10)

David Glasser可以在this blog post中找到一个很好的例子和解释。

嗯,这是(我添加了一些评论):

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开发者工具(时间线标签,内存视图,点击记录)中使用和不使用originalThing进行验证。请注意,上面的示例适用于浏览器和Node.js环境。

还要归功于Vyacheslav Egorov

答案 2 :(得分:2)

我不同意闭包是造成内存泄漏的原因。对于旧版本的IE,这可能是正确的,因为它的垃圾收集很少。请阅读Douglas Crockford撰写的this文章,该文章清楚地说明了内存泄漏的原因。

  

据说没有回收的记忆已经泄露。

泄漏不是问题,高效的垃圾收集是。浏览器和服务器JavaScript应用程序都可能发生泄漏。以V8为例。在浏览器中,当您切换到不同的窗口/选项卡时,垃圾收集会在选项卡上进行。空闲时泄漏堵塞。标签可以闲置。

在服务器上,事情并不那么容易。泄漏可能发生,但GC并不具有成本效益。服务器无法经常使用GC或其性能会受到影响。当节点进程达到某个内存使用量时,它会启动GC。然后将定期清除泄漏。但泄漏仍然可能以更快的速度发生,导致程序崩溃。