问题描述
我正在我的服务器上运行一个 dockerised 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
解决方法
V8 开发者在这里。 --max-old-space-size
标志不直接控制整个进程的内存消耗;它限制了 V8 的托管(即垃圾收集)堆的一部分(最大的部分),这是放置所有 JavaScript 对象的内存块。除了这个“旧空间”,V8还有一个(小得多的)“新空间”;除了V8的托管堆,进程中还有一堆其他的内存消费者:例如,在V8内部,解析器和编译器使用托管堆外的内存;然后除了 V8,Node 也在内存中放置了一堆东西。根据嵌入器(即 Node)和执行代码的具体功能,大字符串和 ArrayBuffers 也可以存在于托管堆之外。
简而言之,--max-old-space-size
为您提供了一个旋钮来影响将使用多少内存,但您在那里设置的限制并没有限制进程的整体内存消耗。 (相比之下,在 Chrome 中,渲染器进程内存的大约三分之一通常是 V8 的托管堆,尽管取决于网站的行为,两个方向都有明显的异常值。我不知道 Node 的典型数字。)
您可以在 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
内存模型元素混淆,因为此输出提供进程的内核视图,而内部内存组织(例如 {{ 1}}) 不可见。
如果你想获得更多关于进程结构(内存映射)的细节,你可以使用node.js
,你会看到当前在 RES 中的内存段,但是这些段也不是与 pmap -x <PID>
段混淆(尽管其中一些可以直接映射到 node.js
段)。
node.js
内存映射,是整个进程内存分配的子集,叫做node.js
,不要与Resident Set
- Resident Memory Size
混淆。实际上,RES
的 Resident Set
更接近于您在 node.js
中看到的值。 VIRT
和 VIRT
之间的差异将归因于 Resident Set
加载的 unix 共享库(不要与 node.js
模块混淆),进程已打开等)。
node.js
进一步分为 Resident Set
、Code Segment
和 Stack Segment
。顾名思义,Heap Segment
包含可执行代码。 Code Segment
包含进程中当前正在运行的线程的堆栈信息。此 blog 提供了对内存组织的良好概述。
所有这些剩下的就是 Stack Segment
。这就是配置参数 Heap Segment
发挥作用的地方。 max-old-space-size
的堆遵循与 Java 垃圾收集内存模型非常相似的内存模型。该参数告诉进程允许有多少对象在连续的垃圾回收周期中存活。然而,这并不意味着这些对象是不可交换的,换句话说,并不是所有设置的 512MB 都将驻留在 node.js
中。这是 RES
的一个子段。 Heap
是 Old Space
的子段。
因此,您在 Heap
中读到的内容与 RES
的 Old Space Sub-Segment
没有直接链接。在短离开应用程序上下文(变量是应用程序未保留的上下文)留下大量垃圾的应用程序中,Heap Segment
将非常小,但是 Old Generation
将迅速增长并且该设置不会帮助您管理进程的内存。