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

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

我有一个在Nodejs中构build的Web抓取器,它收集(相当一部分)数据,使用Cheerio (基本上是jQuery for Node )创build一个对象,然后将其上传到mongoDB。

它工作得很好,除了在较大的网站。 似乎正在发生的是:

  1. 我给刮板一个网上商店的URL刮
  2. 节点转到该URL并从5,000 – 40,000个产品URL中检索任何地方进行刮取
  3. 对于这些新URL中的每一个,Node的request模块获取页面源,然后将数据加载到Cheerio
  4. 使用Cheerio我创build了一个代表产品的JS对象。
  5. 我将对象发送到MongoDB,并保存到我的数据库中。

正如我所说,这发生了成千上万的url,一旦我到达,比如说,10,000个url加载,我得到的节点错误。 最常见的是:

 Node: Fatal JS Error: Process out of memory 

好的,这是实际的问题:

认为这是因为节点的垃圾清理工作不正常。 例如,从所有40,000个URL中删除的request数据可能仍在内存中,或者至less有40,000个创build的JavaScript对象可能是。 也许这也是因为MongoDB连接是在会话开始时进行的,并且从不closures(我只是在所有产品完成后手动closures脚本)。 这是为了避免每次login新产品时打开/closures连接。

要真正确保它们被正确清理(一旦产品进入MongoDB,我不再使用它,可以从内存中删除),我可以/只需要简单地从内存中删除它,只需使用delete product

更重要的是(如果我删除了一个引用的对象是完全从内存中删除,或者我必须删除所有这些对象,我不清楚JS如何处理对象)

例如:

 var saveToDB = require ('./mongoDBFunction.js'); function getData(link){ request(link, function(data){ var $ = cheerio.load(data); createProduct($) }) } function createProduct($) var product = { a: 'asadf', b: 'asdfsd' // there's about 50 lines of data in here in the real products but this is for brevity } product.name = $('.selector').dostuffwithitinjquery('etc'); saveToDB(product); } // In mongoDBFunction.js exports.saveToDB(item){ db.products.save(item, function(err){ console.log("Item was successfully saved!"); delete item; // Will this completely delete the item from memory? }) } 

在javascript中delete不用于删除variables或释放内存。 它仅用于从对象中移除属性。 你可以在delete操作符上find这篇文章 。

您可以通过将variables设置为null来移除对variables中的数据的引用。 如果没有其他的数据引用,那么这将有资格进行垃圾收集。 如果还有其他引用,则不会从内存中清除,直到没有更多引用(例如,代码无法访问它)。

至于什么是导致内存积累,有很多的可能性,我们不能真正看到足够的代码,以知道什么参考可以举行,这将保持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 / 。

JavaScript有一个垃圾收集器,可以自动跟踪哪个variables是“可达”的。 如果一个variables是“可达”的,那么它的值不会被释放。

例如,如果你有一个全局variablesvar g_hugeArray,并且给它分配一个巨大的数组,那么你实际上有两个JavaScript对象:一个是保存数组数据的巨大块。 另一个是窗口对象的名称为“g_hugeArray”的属性,指向该数据。 所以引用链是:window – > g_hugeArray – >实际的数组。

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

当你“closures”时,情况会变得更加复杂。 当您有一个引用局部variables的本地函数时,会创build闭包。 例如:

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

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

在我的应用程序中,我遇到了类似的function相同 我一直在寻找内存泄漏或类似的东西。 我的进程消耗的内存大小达到了1.4 GB,取决于必须下载的链接数量。

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

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

在这里输入图像描述

我的猜测是,当应用程序正在运行时,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)