首页 > 解决方案 > 循环后 Node.js 似乎没有释放内存

问题描述

const axios = require('axios');
const JSDOM = require('jsdom').JSDOM;

axios.get('https://facebook.com')
    .then(response => {
        let i=1;
        while (true) {
            console.log(i++);
            const dom = new JSDOM(response.data);
            dom.window.close();
        }
    });

以上将运行(在我的机器上)440 次,然后崩溃,如下所示:

<--- Last few GCs --->

[13411:0x5dc70d0]    63566 ms: Mark-sweep 2042.3 (2051.1) -> 2041.3 (2051.1) MB, 2407.4 / 0.0 ms  (average mu = 0.123, current mu = 0.027) allocation failure scavenge might not succeed
[13411:0x5dc70d0]    67327 ms: Mark-sweep 2042.0 (2051.1) -> 2041.1 (2050.9) MB, 3745.2 / 0.0 ms  (average mu = 0.055, current mu = 0.004) allocation failure scavenge might not succeed


<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x145cc79]
    1: StubFrame [pc: 0x145dab5]
Security context: 0x0d2e98240921 <JSObject>
    2: /* anonymous */ [0x374dcc798739] [/pathtomyproject/node_modules/parse5/lib/tokenizer/index.js:~644] [pc=0x323d7cd98d24](this=0x10b528b73279 <Tokenizer map = 0x242f38ec5c11>,95)
    3: getNextToken [0x374dcc799419] [/pathtomyproject/node_modules/parse5/lib/tokenizer/i...

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Writing Node.js report to file: report.20200215.163602.13411.0.001.json
Node.js report completed
 1: 0xa9d570 node::Abort() [node]
 2: 0xa9f832 node::OnFatalError(char const*, char const*) [node]
 3: 0xc0758e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xc07909 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xdb5e15  [node]
 6: 0xdb64a6 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [node]
 7: 0xdc4d19 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]
 8: 0xdc5b55 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 9: 0xdc862c v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]
10: 0xd8f204 v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]
11: 0x10dc52e v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node]
12: 0x145cc79  [node]
Aborted (core dumped)

所以我猜即使dom在每次迭代重新分配之后,以前的值仍然存储在堆上。为什么会这样,我该如何预防?

标签: javascriptnode.jsmemoryaxiosjsdom

解决方案


V8 中的垃圾收集是在您的代码未运行并且介于执行操作之间时完成的。您的while(true)循环将永远运行,因此您永远不会对 V8 进行更改来进行垃圾收集。

由于许多其他原因,您永远不会在 Javascript 中执行一个永远运行的循环(除非您有一个await内部循环),因为 nodejs 是一个主要的单线程事件驱动系统,当您忙于while(true)循环时,没有事件可以永远得到处理,所以很多事情无法在 nodejs 中完成。

为什么会这样,我该如何预防?

垃圾收集器通常被设计为让你的代码运行而不干扰它们的执行,然后,当你的代码没有做任何事情并且完成了它正在做的事情的内部状态时,垃圾收集器检查状态堆中的对象以查找不再使用的对象。

您可以通过在没有提供 GC 可以运行的任何空闲时间的情况下不进入这样的紧密循环来防止这种情况。如上所述,像这样的紧密循环会阻止 nodejs 中的任何其他事件无论如何运行,因此这通常也是其他原因的不好原因。

请注意,您的代码不仅创建了 444 个dom实例以及与这些实例关联的所有对象,而且它不提供任何能力来清理在创建该dom实例时使用的所有临时变量/字符串,这在解析 HTML,因此总内存使用量明显高于仅完成的 444 个dom实例。如果您在循环中插入任何类型的呼吸空间,那么 GC 将有机会清理不再使用的东西。


推荐阅读