Node JS应用内存压力上升

Node JS application rising memory pressure

我正在开发 Node JS 应用程序并面临严重的内存压力问题。以下是启动应用程序(在恒定负载下)60 分钟内的内存使用趋势:

内存使用率急剧上升至 95%,但之后保持相当稳定。

我之前和之后进行了内存转储,但我捕获转储的方式似乎存在一些问题,因为两个转储文件之间的差异只有几 MB。

下面是堆转储在 chrome 分析中的样子:

但我对此很陌生,不确定我应该寻找什么。

有人可以提供有关如何找出此处的内存问题、如何检测内存泄漏或如何理解此处提到的堆转储文件的任何指示吗?

节点版本:0.12.14

- find node_modules -type f -name "*.node" - 的输出如下:

node_modules/simpleflake/node_modules/bignum/build/Release/bignum.node
node_modules/simpleflake/node_modules/bignum/build/Release/obj.target/bignum.node
node_modules/heapdump/build/Release/addon.node
node_modules/heapdump/build/Release/obj.target/addon.node
node_modules/couchbase/build/Release/couchbase_impl.node

- npm list --depth=0 - 的输出如下:

aws-sdk@2.5.3
body-parser@1.11.0
check-types@6.0.0
couchbase@2.2.2
elasticsearch@10.1.3
expect@1.20.2
express@4.14.0
jshint@2.9.3
minimist@1.1.3
mocha@2.5.3
moment@2.9.0
morgan@1.5.3
newrelic@1.30.0
request@2.53.0
simpleflake@1.0.0
underscore@1.7.0
why-is-node-running@1.2.2 (https://github.com/mindtickle/why-is-node-running.git#96f3c8da54b110e8a9a1423361d2da7c125784f6)
winston@1.0.2
winston-aws-cloudwatch@0.4.2

此外,是否可以查看我正在使用的任何包是否对内存泄漏负责?

提前致谢。

要查看的两个主要领域是对象保留和本机泄漏。这适用于大多数垃圾收集语言运行在虚拟机上

最有可能的是,您的应用程序或模块中的某些内容保留了对对象的引用并填充了对象 space。

接下来是使用本机代码和泄漏本机内存的模块,这些模块不会出现在 GC 对象中 space。

然后 Node.js 本身可能有原生泄漏,由于用户数量众多,这不太可能,但总是有可能,特别是对于旧版本的 Node.js。

应用程序内存和垃圾收集

运行 您的应用程序打开了节点垃圾收集日志记录

node --trace_gc --trace_gc_verbose app.js

这将提供有关每个 GC 事件的信息块。主要信息是第一行,它告诉您在 GC 之前 Node.js 和 GC 之后 -> 使用了多少内存。

[97577:0x101804a00]    38841 ms: Scavenge 369.0 (412.2) -> 356.0 (414.2) MB, 5.4 / 0 ms [allocation failure].

--trace_gc_verbose 为您提供了此后的所有行,并提供了每个内存的更多详细信息 space。

[97577:0x101804a00] Memory allocator,   used: 424180 KB, available: 1074956 KB
[97577:0x101804a00] New space,          used:    789 KB, available:  15334 KB, committed:  32248 KB
[97577:0x101804a00] Old space,          used: 311482 KB, available:      0 KB, committed: 321328 KB
[97577:0x101804a00] Code space,         used:  22697 KB, available:   3117 KB, committed:  26170 KB
[97577:0x101804a00] Map space,          used:  15031 KB, available:   3273 KB, committed:  19209 KB
[97577:0x101804a00] Large object space, used:  14497 KB, available: 1073915 KB, committed:  14640 KB
[97577:0x101804a00] All spaces,         used: 364498 KB, available: 1095640 KB, committed: 413596 KB
[97577:0x101804a00] External memory reported:  19448 KB
[97577:0x101804a00] Total time spent in GC  : 944.0 ms

绘制这些值通常看起来像锯齿。这种“锯齿状”发生在多个级别,因为不同的内存 space 被填充,达到限制,然后被垃圾回收。此图来自Dynatraces About Performance blog post on Understanding Garbage Collection and hunting Memory Leaks in Node.js shows an app slowly increasing it's object space

随着时间的推移,您应该会看到哪些内存区域在增长,哪些没有增长,这可以为您提供一些关于哪些代码保留对象引用的上下文。如果您在堆中没有看到任何内存增长,则可能是本机模块或 Node.js 本身发生了真正的内存泄漏。

处理内存

node 应用程序报告的垃圾收集器使用的内存,称为“堆”,并不总是与 OS 报告的进程相匹配。 OS 可以分配更大数量的内存,而 Node.js 当前可能没有使用。这可能是正常的,如果 OS 没有内存压力并且 Node.js 垃圾收集对象会有不同,因为 OS 保留额外的内存 space过程。当发生泄漏并且分配的进程内存不断增长时,这也可能是异常的。

收集一些 OS 内存信息以及您的应用程序

node --trace_gc app.js &
pid=$!

while sleep 15; do
  pss=$(sudo awk '/^Pss/{p=p+}END{print p}' /proc/$pid/smaps)
  echo "$(date) pss[$pss]"
done

这将为您提供一个日期和内存值,以字节为单位,与您的 GC 输出一致以进行比较:

[531:0x101804a00]    12539 ms: Scavenge 261.6 (306.4) -> 246.6 (307.4) MB, 5.0 / 0 ms [allocation failure].
Tue Aug 30 12:34:46 UTC 2016 pss[3396192]

smaps 中的 Pss 数字考虑了共享内存重复,因此比 ps

使用的 Rss 数字更准确

堆转储

如果您确实发现垃圾收集日志内存与 OS 进程内存增长相匹配,那么很可能是应用程序或它所依赖的模块保留了对旧对象的引用,您需要开始查看堆转储以确定这些对象是什么。

在调试模式下使用 Chrome dev tools 和 运行 您的应用程序,以便您可以附加到它并创建堆转储。

node --inspect app.js

当检测到节点进程时,您会在左上方看到一个小节点图标,单击该图标可打开专用于节点应用程序的 Devtools window。在应用程序启动时获取堆快照。内存增长中间的某个地方。然后接近最大时(崩溃前!)。

比较 3 个快照,在后面两个转储中查找大对象或大量重复对象引用与第一个转储。


本机泄漏

如果 GC 日志没有显示内存增加但节点进程 OS 显示内存增加,则可能是本机泄漏。

OSX 有一个有用的开发工具,叫做 leaks that can find unreferenced memory in a process without full debugging. I believe valgrind can do the same type of thing with --leak-check=yes

这些可能无法确定问题所在,只是确认问题是本机泄漏。

模块

要在您的应用中查找本机模块,请使用以下命令:

find node_modules -type f -name "*.node"

对于这些模块中的每一个,生成一个小的测试用例,说明每个模块在您的应用程序中的使用方式,并在检查泄漏的同时对其进行压力测试。

您也可以尝试暂时从您的应用程序中排除或禁用任何本机模块,以排除它们或将它们突出显示为原因

Node.js

如果可能,请尝试 Node.js 的不同版本。移动到最新的主要版本,最新的 minor/patch,或返回主要版本以查看内存配置文件是否有任何改进或更改。

在 v0.x 或 v6.x Node.js 版本中,通用内存管理有了很大改进。如果您可以更新,该问题可能会通过错误修复或 V8 更新而消失。