62

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

我知道关闭可能是一个问题。

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

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

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

4

3 回答 3

44

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

此外,Mozilla 发表了一篇关于在 Node.js代码中查找内存泄漏的精彩文章。我假设如果您尝试他们的一些策略,您可以找到表达泄漏行为的代码部分。最佳实践很好,但我认为了解您的程序需求并根据您可以经验观察的内容提出一些个性化的最佳实践会更有帮助。

以下是 Mozilla 文章的简短摘录:

  • Jimb Esser's node-mtrace,它使用 GCCmtrace实用程序来分析堆使用情况。
  • Dave Pacheconode-heap-dump拍摄了 V8 堆的快照,并将整个内容序列化为一个巨大的 JSON 文件。它包括在 JavaScript 中遍历和调查生成的快照的工具。
  • Danny Coates为 V8 分析器提供节点绑定,v8-profilernode-inspector使用 WebKit Web Inspector 提供节点调试接口。
  • Felix Gnass 的叉子,用于取消禁用保持器图
  • Felix Geisendörfer 的 Node Memory Leak Tutorial 是关于如何使用v8-profilerand的简短而巧妙的解释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 而言无关紧要。我对此还没有明确的答案;我只是发布我在网络上找到的内容。

于 2013-10-30T14:47:31.603 回答
14

您可以在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);

originalThing请在 Chrome 开发工具(时间线选项卡、内存视图、点击记录)中尝试使用和不使用归零。请注意,上面的示例适用于浏览器和 Node.js 环境。

也特别感谢 Vyacheslav Egorov

于 2014-03-03T12:29:12.477 回答
2

我不得不不同意闭包是内存泄漏的原因。这对于旧版本的 IE 来说可能是正确的,因为它的垃圾收集质量很差。请阅读Douglas Crockford 的这篇文章,它清楚地说明了内存泄漏是什么。

没有回收的内存被称为泄漏。

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

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

于 2013-10-24T10:57:57.823 回答