jsdom 和 node.js 内存泄漏

问题描述 投票:0回答:6

我发现了一些关于有类似问题的人的参考,其中答案总是,确保完成后调用 window.close() 。然而,这似乎对我不起作用(节点 0.8.14 和 jsdom 0.3.1)

一个简单的重现

var util = require('util');
var jsdom=require('jsdom');

function doOne() {
  var htmlDoc = '<html><head></head><body id="' + i + '"></body></html>';
  jsdom.env(htmlDoc, null, null, function(errors, window) {
    window.close();
  });
}

for (var i=1;i< 100000;i++ )  {
  doOne();
  if(i % 500 == 0)  {
    console.log(i + ":" + util.inspect(process.memoryUsage()));
  }
}
console.log ("done");

我得到的输出是

500:{ rss: 108847104, heapTotal: 115979520, heapUsed: 102696768 }
1000:{ rss: 198250496, heapTotal: 194394624, heapUsed: 190892120 }
1500:{ rss: 267304960, heapTotal: 254246912, heapUsed: 223847712 }
...
11000:{ rss: 1565204480, heapTotal: 1593723904, heapUsed: 1466889432 }

此时风扇开始疯狂运转,测试实际上停止了……或者至少开始变得非常缓慢

除了 window.close 之外,还有谁有其他技巧来消除内存泄漏(或者它看起来确实像内存泄漏)

谢谢!

彼得

node.js memory-leaks jsdom
6个回答
15
投票

使用jsdom 0.6.0帮助抓取一些数据,遇到了同样的问题。

window.close
只能帮助减缓内存泄漏,但它最终确实会蔓延,直到进程被杀死。

运行脚本

node --expose-gc myscript.js

在他们修复内存泄漏之前,除了调用

window.close
之外,手动调用垃圾收集器似乎也有效:

if (process.memoryUsage().heapUsed > 200000000) { // memory use is above 200MB
    global.gc();
}

在调用 window.close 后卡住了。每次触发时,内存使用立即回落到基线(对我来说大约 50MB)。几乎察觉不到停顿。

更新:还可以考虑连续多次调用

global.gc()
而不是只调用一次(即
global.gc();global.gc();global.gc();global.gc();global.gc();

多次调用 window.gc() 更有效(基于我不完善的测试),我怀疑是因为它可能导致 chrome 触发一个主要的 GC 事件而不是一个次要的事件。 - https://github.com/cypress-io/cypress/issues/350#issuecomment-688969443


7
投票

您没有给程序任何空闲时间来进行垃圾收集。我相信您会遇到同样的问题,任何在循环中紧密创建的大型对象图都不会中断。

CheapSteaks 的答案证实了这一点,它手动强制垃圾收集。如果可行的话,jsdom 中不可能存在内存泄漏,因为根据定义,内存泄漏会阻止垃圾收集器收集泄漏的内存。


4
投票

我在 jsdom 上遇到了同样的问题,然后切换到 cheerio,它比 jsdom 快得多,甚至在扫描数百个站点后也能工作。也许你也应该尝试一下。唯一的问题是,它没有您可以在 jsdom 中使用的所有选择器。

希望它也适合你。

丹尼尔


1
投票

with gulp、内存使用、清理、变量删除、window.close()

var gb = setInterval(function () {

    //only call if memory use is bove 200MB
    if (process.memoryUsage().heapUsed > 200000000) { 
        global.gc();
    }

}, 10000); // 10sec


gulp.task('tester', ['clean:raw2'], function() {

  return gulp.src('./raw/*.html')
    .pipe(logger())
    .pipe(map(function(contents, filename) {


        var doc = jsdom.jsdom(contents);
        var window = doc.parentWindow;
        var $ = jquery(window);

        console.log( $('title').text() );

        var html = window.document.documentElement.outerHTML;

        $( doc ).ready(function() {
            console.log( "document loaded" );
            window.close();
        });

        return html;
    }))
    .pipe(gulp.dest('./raw2'))
    .on('end', onEnd);
});

对于 7k 个文件,我的使用量始终在 200mb - 300mb 之间。花了30分钟。 这可能对某人有帮助,因为我用谷歌搜索并没有找到任何有用的东西。


1
投票

解决此问题的方法是在分叉的 child_process 中运行 jsdom 相关代码,并在完成后发回相关结果。然后杀死child_process。


0
投票

JSDom 使用

process.nextTick
推迟某些操作。

在同步循环中调用 JSDom 时,事件循环永远不会运行。 因此,对旧 JSDom 窗口对象的引用被保留,节点内存不足。

通过运行一些异步代码,事件循环将被允许运行,并且应该解决内存泄漏问题。

您可以简单地使用零延迟的

setTimeout
来允许事件循环运行。

await new Promise(resolve => setTimeout(resolve, 0));
© www.soinside.com 2019 - 2024. All rights reserved.