进程内存不足时删除大型Javascript对象

时间:2013-11-21 02:45:04

标签: javascript node.js mongodb memory-management memory-leaks

我是这种javascript的新手,所以我会给出一个简短的解释:

我有一个内置于Nodejs的网络抓取工具,可以收集(相当多)数据,并使用Cheerio处理它(基本上jQueryNode)创建一个对象然后将其上传到mongoDB。

它工作得很好,除了在较大的网站上。 出现的内容是:

  1. 我给刮刀一个在线商店的网址来刮
  2. 节点转到该网址,并检索5,000到40,000个产品网址中的任何地方,以便
  3. 对于这些新网址中的每一个,节点的request模块获取页面源,然后将数据加载到Cheerio
  4. 使用Cheerio我创建了一个代表产品的JS对象。
  5. 我将对象发送到MongoDB,并将其保存到我的数据库中。
  6. 正如我所说,这发生在成千上万的URL上,一旦我到达,比如说,加载了10,000个网址,我就会在节点中出错。最常见的是:

    Node: Fatal JS Error: Process out of memory
    

    好的,这是实际的问题:

    认为这种情况正在发生,因为Node的垃圾清理工作不正常。例如,从所有40,000个网址中删除的request数据仍然可能在内存中,或者至少可以创建40,000个javascript对象。也许这也是因为MongoDB连接是在会话开始时进行的,并且永远不会关闭(我只需在完成所有产品后手动关闭脚本)。这是为了避免每次登录新产品时都打开/关闭连接。

    要真正确保它们被正确清理(一旦产品进入MongoDB我不再使用它并且可以从内存中删除)可以/我应该只是简单地从内存中删除它,只需使用{{1} }?

    Moreso(我显然不知道JS如何处理对象)如果我删除一个对象的引用是完全从内存中删除,还是我必须删除所有这些?

    例如:

    delete product

4 个答案:

答案 0 :(得分:10)

javascript中的

delete不用于删除变量或空闲内存。它仅用于从对象中删除属性。您可以在delete运算符上找到this article一个很好的阅读。

您可以通过将变量设置为null来删除对变量中保存的数据的引用。如果没有其他对该数据的引用,那么这将使其符合垃圾收集的条件。如果有对该对象的其他引用,那么在没有更多引用之前它将不会从内存中清除(例如,代码无法访问它)。

至于导致内存累积的原因,有很多可能性,我们无法真正看到你的代码,知道可以保留哪些引用可以防止GC释放出来。

如果这是一个没有中断执行的单个长时间运行的进程,您可能还需要手动运行垃圾收集器,以确保它有机会清理已发布的内容。

以下是关于在node.js中跟踪内存使用情况的几篇文章:http://dtrace.org/blogs/bmc/2012/05/05/debugging-node-js-memory-leaks/https://hacks.mozilla.org/2012/11/tracking-down-memory-leaks-in-node-js-a-node-js-holiday-season/

答案 1 :(得分:3)

JavaScript有一个垃圾收集器,可以自动跟踪哪个变量“可以访问”。如果变量“可达”,则不会释放其值。

例如,如果你有一个全局变量var g_hugeArray并且你为它分配了一个庞大的数组,那么你实际上有两个JavaScript对象:一个是保存数组数据的巨大块。另一个是窗口对象上的属性,其名称为“g_hugeArray”,指向该数据。所以参考链是:window - > g_hugeArray - >实际的数组。

为了释放实际数组,可以使实际数组“无法访问”。你可以打破任何链接上面的链来实现这一目标。如果将g_hugeArray设置为null,则会断开g_hugeArray与实际数组之间的链接。这使得数组数据无法访问,因此它将在垃圾收集器运行时释放。或者,您可以使用“delete window.g_hugeArray”从窗口对象中删除属性“g_hugeArray”。这打破了窗口和g_hugeArray之间的链接,也使实际数组无法访问。

当你有“关闭”时,情况变得更加复杂。当您具有引用局部变量的本地函数时,将创建闭包。例如:

function a()
{
    var x = 10;
    var y = 20;
    setTimeout(function()
        {
            alert(x);
        }, 100);
}

在这种情况下,即使在函数“a”返回后,仍然可以从匿名超时函数到达局部变量x。如果没有超时功能,则只要函数a返回,则局部变量x和y都将变为不可达。但是匿名函数的存在改变了这一点。根据JavaScript引擎的实现方式,它可以选择保留变量x和y(因为它不知道函数是否需要y,直到函数实际运行,这发生在函数a返回之后)。或者,如果它足够聪明,它只能保持x。想象一下,如果x和y都指向大事,那么这可能是一个问题。因此,关闭非常方便,但有时它更有可能导致内存问题,并且可能使跟踪内存问题变得更加困难。

答案 2 :(得分:1)

我在具有类似功能的应用程序中遇到了同样的问题。我一直在寻找内存泄漏或类似的东西。我的进程消耗的内存大小已达到1.4 GB,并且取决于必须下载的链接数。

我注意到的第一件事是在手动运行垃圾收集器之后,几乎所有内存都被释放了。我下载的每个页面大约1 MB,已经处理并存储在数据库中。

然后我安装heapdump并查看应用程序的快照。有关内存分析的更多信息,请参阅Webstorm Blog

enter image description here

我的猜测是,当应用程序运行时,GC无法启动。为此,我开始使用标志--expose-gc运行应用程序,并在程序实施时开始手动运行GC。

const runGCIfNeeded = (() => {
    let i = 0;
    return function runGCIfNeeded() {
        if (i++ > 200) {
            i = 0;

            if (global.gc) {
                global.gc();
            } else {
                logger.warn('Garbage collection unavailable. Pass --expose-gc when launching node to enable forced garbage collection.');
            }
        }
    };
})();

// run GC check after each iteration
checkProduct(product._id)
    .then(/* ... */)
    .finally(runGCIfNeeded)

答案 3 :(得分:0)

有趣的是,如果您在全局范围内定义某些内容时未使用constletvar等,则它似乎是全局对象的属性,并删除返回true。这可能导致其被垃圾回收。我进行了这样的测试,它似乎会对我的内存使用产生预期的影响,请告诉我这是否不正确或您得到的结果是否完全不同:


x = [];
process.memoryUsage();
i = 0;
while(i<1000000) {
    x.push(10.5);
}
process.memoryUsage();
delete x
process.memoryUsage();