在进程内存不足时删除大型Javascript对象
我是这种javascript的新手,所以我将给出一个简短的解释: 我有一个内置的网络刮板在进程内存不足时删除大型Javascript对象,javascript,node.js,mongodb,memory-management,memory-leaks,Javascript,Node.js,Mongodb,Memory Management,Memory Leaks,我是这种javascript的新手,所以我将给出一个简短的解释: 我有一个内置的网络刮板Nodejs,它收集(相当多的)数据,用Cheerio处理它(基本上是jQueryforNode)创建一个对象,然后将其上传到mongoDB 除了在更大的网站上,它工作得很好。似乎正在发生的是: 我给刮刀一个在线商店的URL来刮刀 节点转到该URL并检索5000-40000个产品URL中的任意位置以进行刮取 对于每个新URL,节点的请求模块获取页面源,然后将数据加载到Cheerio 我使用Cheerio创建了
Nodejs
,它收集(相当多的)数据,用Cheerio
处理它(基本上是jQuery
forNode
)创建一个对象,然后将其上传到mongoDB
除了在更大的网站上,它工作得很好。似乎正在发生的是:
请求
模块获取页面源,然后将数据加载到Cheerio
李>
Node: Fatal JS Error: Process out of memory
好的,下面是实际问题:
我认为这是因为节点的垃圾清理工作不正常。例如,从所有40000个URL中刮取的请求
数据可能仍在内存中,或者至少40000个创建的javascript对象可能在内存中。也许这也是因为MongoDB连接是在会话开始时建立的,从未关闭过(我只是在所有产品完成后手动关闭脚本)。这是为了避免每次登录新产品时打开/关闭连接
为了确保正确地清理它们(一旦产品进入MongoDB,我就不再使用它,并且可以从内存中删除),我可以/应该只从内存中删除它,只需使用删除产品
Moreso(我显然不知道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
不用于删除变量或释放内存。它仅用于从对象中删除属性。您可以在delete
操作符上找到一个很好的读物
通过将变量设置为类似于null
的值,可以删除对变量中保存的数据的引用。如果没有对该数据的其他引用,那么这将使其符合垃圾收集的条件。如果存在对该对象的其他引用,则不会从内存中清除该对象,直到不再存在对该对象的引用(例如,代码无法访问该对象)
至于导致内存累积的原因,有很多种可能性,我们无法真正看到足够多的代码,以了解哪些引用可能会保留在GC上,从而阻止GC释放一些内容
如果这是一个长时间运行的进程,执行过程中没有中断,那么您可能还需要手动运行垃圾收集器,以确保它有机会清理您发布的内容
这里有几篇文章介绍如何在node.js:和中跟踪内存使用情况。JavaScript有一个垃圾收集器,可以自动跟踪哪个变量是“可访问的”。如果一个变量是“可到达的”,那么它的值就不会被释放 例如,如果您有一个全局变量var g_hugarray,并为其分配一个巨大的数组,那么这里实际上有两个JavaScript对象:一个是保存数组数据的巨大块。另一个是窗口对象上的属性,其名称为“g_HugarRay”,指向该数据。因此,引用链是:window->g_hugarray->实际数组 为了释放实际数组,您将实际数组设置为“不可访问”。您可以断开上面的任一链接来实现此目的。如果将g_HugarRay设置为null,则会断开g_HugarRay与实际数组之间的链接。这使得数组数据无法访问,因此当垃圾回收器运行时,它将被释放。或者,您可以使用“delete window.g_hugarray”从窗口对象中删除属性“g_hugarray”。这会断开window和g_Hugarray之间的链接,并使实际数组无法访问 如果有“闭包”,情况会变得更复杂。当您有一个引用局部变量的局部函数时,就会创建闭包。例如:
function a()
{
var x = 10;
var y = 20;
setTimeout(function()
{
alert(x);
}, 100);
}
在这种情况下,即使在函数“a”返回后,仍然可以从匿名超时函数访问局部变量x。如果没有timeout函数,那么一旦函数a返回,局部变量x和y都将变得不可访问。但是匿名函数的存在改变了这一点。根据JavaScript引擎的实现方式,它可能会选择同时保留变量x和y(因为在函数实际运行之前,它不知道函数是否需要y,而实际运行是在函数a返回之后发生的)。或者如果它足够聪明,它只能保存x。想象一下,如果x和y都指向大的东西,这可能是一个问题。因此,关闭非常方便,但有时更可能导致内存问题,并使跟踪内存问题变得更加困难。我在具有类似功能的应用程序中遇到了同样的问题。我一直在寻找内存泄漏之类的东西。我的进程消耗的内存大小已达到1.4 GB,这取决于必须下载的链接数 我注意到的第一件事是,在手动运行垃圾收集器之后,几乎所有内存都被释放。我下载的每个页面大约占用1MB,经过处理并存储在数据库中 然后我安装并查看了应用程序的快照。有关内存分析的更多信息,请访问 我的猜测是,当应用程序运行时,GC不会启动。为此,我开始使用标志
--expose 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)