常驻内存大于--max-old-space-size 阈值?

Resident memory greater than --max-old-space-size threshold?

我在我的服务器上 运行 一个码头化的 node.js 应用程序,使用 --max-old-space-size 选项来限制应用程序堆大小。以下输出由 htop 给出:

  PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
10158 root       20   0  4284   720   644 S  0.0  0.0  0:00.00 sh -c node --max-old-space-size=512 ./dist/www.js
10159 root       20   0 1841M  929M 29592 S  0.0  3.0  1h31:16 node --max-old-space-size=512 ./dist/www.js
10160 root       20   0 1841M  929M 29592 S  0.0  3.0  0:00.00 node --max-old-space-size=512 ./dist/www.js
10161 root       20   0 1841M  929M 29592 S  0.0  3.0  7:27.13 node --max-old-space-size=512 ./dist/www.js
10162 root       20   0 1841M  929M 29592 S  0.0  3.0  7:26.96 node --max-old-space-size=512 ./dist/www.js
10163 root       20   0 1841M  929M 29592 S  0.0  3.0  7:26.99 node --max-old-space-size=512 ./dist/www.js
10164 root       20   0 1841M  929M 29592 S  0.0  3.0  7:26.64 node --max-old-space-size=512 ./dist/www.js

您可以看到我的应用程序驻留内存 (929M) 远远高于我的 max-old-space-size 值 (512MB) 那么为什么会出现这种情况?此时应用程序不应该中止吗?

系统信息

Docker version: 19.03.5
node image version: 11.13.0

uname -v
#31~18.04.1-Ubuntu SMP Tue Nov 17 10:48:34 UTC 2020

您可以在 top 上看到的 RES 是 linux 进程的虚拟内存的非交换区域(有关详细信息,请参阅 man top)。这是解释的简短片段

22. RES  --  Resident Memory Size (KiB)
     A subset of the virtual address space (VIRT) representing the non-swapped 
physical memory a task is currently using.  It is also the sum of the 
RSan, RSfd and RSsh fields.

     It  can  include private anonymous pages, private pages mapped to files 
(including program images and shared libraries) plus shared anonymous pages.  
All such memory is backed by the swap file represented separately under SWAP.

top 的输出不应与 node.js 内存模型元素混淆,因为此输出给出进程的内核视图,而内部内存组织(如本例中的node.js) 不可见。

如果您想获得有关进程结构(内存映射)的更多详细信息,您可以使用 pmap -x <PID> 并且您将看到当前哪些内存段在 RES 中,但是这些段也是不要与 node.js 段混淆(尽管其中一些可以直接映射到 node.js 段)。

node.js 内存映射,它是分配的整个进程内存的子集,称为 Resident Set,不应与 Resident Memory Size - RES 混淆。事实上,node.jsResident Set 更接近您在 VIRT 中看到的值。 VIRTResident Set 之间的差异将归因于由 node.js 加载的 unix 共享库(不要与 node.js 模块混淆),文件进程已打开等)。

Resident Set又分为Code SegmentStack SegmentHeap Segment。顾名思义,Code Segment 包含可执行代码。 Stack Segment 包含进程中当前 运行 个线程的堆栈信息。 blog 很好地概述了内存组织。

所有这些剩下的就是 Heap Segment。这里是配置参数 max-old-space-size 发挥作用的地方。 node.js 的堆遵循与 Java 垃圾收集内存模型非常相似的内存模型。该参数告诉进程有多少对象被允许在连续的垃圾收集周期中存活下来。然而,这并不意味着这个对象是不可交换的,换句话说,并不是说所有设置的 512MB 都将驻留在 RES 中。这是 Heap 的子段。 Old SpaceHeap.

的子片段

因此,您在 RES 中阅读的内容没有直接 link 到 Heap SegmentOld Space Sub-Segment。在短期离开应用程序上下文(变量是应用程序未保留的上下文)留下大量垃圾的应用程序中,Old Generation 会非常小,但是 RES 会快速增长并且该设置不会帮助您管理进程的内存。

这里是 V8 开发人员。 --max-old-space-size 标志不直接控制整个进程的内存消耗;它限制了 V8 托管(即垃圾收集)堆的一部分(最大部分),这是放置所有 JavaScript 对象的内存块。除了这个“old space”,V8 还有一个(小得多的)“new space”;除了 V8 的托管堆之外,还有一堆其他的内存消费者在这个过程中:例如,在 V8 中,解析器和编译器使用托管堆之外的内存;然后除了 V8,Node 还在内存中放了一堆东西。根据嵌入器(即节点)和执行代码的具体作用,大字符串和 ArrayBuffers 也可以存在于托管堆之外。

简而言之,--max-old-space-size给你一个旋钮来影响将使用多少内存,但你设置的限制对进程没有限制'整体内存消耗。 (相比之下,在 Chrome 中,通常渲染器进程内存的三分之一是 V8 的托管堆,尽管根据网站的不同,两个方向都有明显的异常值。我不知道 Node.js 的典型数字。 )