为什么 Node.js 启动后虚拟内存很大

简介: 问题来源 问题来源于cnode社区:node启动占用内存的问题。 自己本地跑了一下,乖乖不得了,启动一个 node,什么都不做。结果是这样子的:果然900+M。 我的电脑信息: $cat /proc/version Linux version 4.

问题来源

问题来源于cnode社区:node启动占用内存的问题

自己本地跑了一下,乖乖不得了,启动一个 node,什么都不做。结果是这样子的:果然900+M

undefined

我的电脑信息:

$cat /proc/version
Linux version 4.13.0-38-generic (buildd@lgw01-amd64-027) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9)) #43~16.04.1-Ubuntu SMP Wed Mar 14 17:48:43 UTC 2018
$cat /proc/cpuinfo | grep processor
processor    : 0
processor    : 1
processor    : 2
processor    : 3
AI 代码解读

我们知道,进程真正从OS拿到的内存是RSS,一般意义上讲,进程占了多少内存就是这个RSS。因此,cnode 社区的这个标题有点欠妥。

那么我就求证一下 node 到底在哪里申请了这么多虚拟内存。

求证过程

pmap 查看虚拟内存使用情况

下面命令输出中把一些100KB以下的信息省略,有兴趣的同学可以自己查看。

$ pmap -d 28708
28708:   ./node
Address           Kbytes Mode  Offset           Device    Mapping
0000000000400000   28540 r-x-- 0000000000000000 008:00005 node
00000000021df000       4 r---- 0000000001bdf000 008:00005 node
00000000021e0000     104 rw--- 0000000001be0000 008:00005 node
00000000021fa000    2168 rw--- 0000000000000000 000:00000   [ anon ]
0000000003bd6000    1352 rw--- 0000000000000000 000:00000   [ anon ]
0000031078d80000     512 rw--- 0000000000000000 000:00000   [ anon ]
000003cf96a00000     512 rw--- 0000000000000000 000:00000   [ anon ]
00000439e7900000     512 rw--- 0000000000000000 000:00000   [ anon ]
0000083024e00000     512 ----- 0000000000000000 000:00000   [ anon ]
000008c587300000     512 rw--- 0000000000000000 000:00000   [ anon ]
00000b1f3fb00000     512 ----- 0000000000000000 000:00000   [ anon ]
00000f247cf80000     512 rw--- 0000000000000000 000:00000   [ anon ]
0000169de244f000     196 ----- 0000000000000000 000:00000   [ anon ]
0000169de2485000     492 ----- 0000000000000000 000:00000   [ anon ]
0000169de2505000     492 ----- 0000000000000000 000:00000   [ anon ]
0000169de2585000     492 ----- 0000000000000000 000:00000   [ anon ]
0000169de2604000     492 rwx-- 0000000000000000 000:00000   [ anon ]
0000169de2684000     492 rwx-- 0000000000000000 000:00000   [ anon ]
0000169de2704000     492 rwx-- 0000000000000000 000:00000   [ anon ]
0000169de27ff000  520512 ----- 0000000000000000 000:00000   [ anon ]
000017db76f80000     316 rw--- 0000000000000000 000:00000   [ anon ]
0000219337b00000     512 rw--- 0000000000000000 000:00000   [ anon ]
000025cdf0280000     512 rw--- 0000000000000000 000:00000   [ anon ]
000025e610580000     512 ----- 0000000000000000 000:00000   [ anon ]
000026bff1500000     512 rw--- 0000000000000000 000:00000   [ anon ]
000028eaed980000     512 ----- 0000000000000000 000:00000   [ anon ]
0000309c9b900000     512 rw--- 0000000000000000 000:00000   [ anon ]
000031a7c5980000     512 ----- 0000000000000000 000:00000   [ anon ]
0000389d07380000     512 rw--- 0000000000000000 000:00000   [ anon ]
00003a0dee480000     512 rw--- 0000000000000000 000:00000   [ anon ]
00007f1630000000     132 rw--- 0000000000000000 000:00000   [ anon ]
00007f1630021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f1634000000     132 rw--- 0000000000000000 000:00000   [ anon ]
00007f1634021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f1638ffb000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f16397fc000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f1639ffd000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f163a7fe000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f163afff000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f163b800000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f163c000000     132 rw--- 0000000000000000 000:00000   [ anon ]
00007f163c021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f1640000000     132 rw--- 0000000000000000 000:00000   [ anon ]
00007f1640021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f1644000000     132 rw--- 0000000000000000 000:00000   [ anon ]
00007f1644021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f1648713000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f1648f14000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f1649715000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f1649f16000    8192 rw--- 0000000000000000 000:00000   [ anon ]
00007f164a716000    1792 r-x-- 0000000000000000 008:00005 libc-2.23.so
00007f164a8d6000    2048 ----- 00000000001c0000 008:00005 libc-2.23.so
00007f164aaf8000    2044 ----- 0000000000018000 008:00005 libpthread-2.23.so
00007f164ad13000    2044 ----- 0000000000016000 008:00005 libgcc_s.so.1
00007f164af13000    1056 r-x-- 0000000000000000 008:00005 libm-2.23.so
00007f164b01b000    2044 ----- 0000000000108000 008:00005 libm-2.23.so
00007f164b21c000    1480 r-x-- 0000000000000000 008:00005 libstdc++.so.6.0.21
00007f164b38e000    2048 ----- 0000000000172000 008:00005 libstdc++.so.6.0.21
00007f164b5a5000    2044 ----- 0000000000007000 008:00005 librt-2.23.so
00007f164b7a9000    2044 ----- 0000000000003000 008:00005 libdl-2.23.so
00007f164b9aa000     152 r-x-- 0000000000000000 008:00005 ld-2.23.so
00007ffc8810d000     136 rw--- 0000000000000000 000:00000   [ stack ]
mapped: 994068K    writeable/private: 94540K    shared: 0K
AI 代码解读

最后一行的信息:

  • mapped 表示该进程映射的虚拟地址空间大小,也就是该进程预先分配的虚拟内存大小,即ps出的vsz
  • writeable/private 表示进程所占用的私有地址空间大小,也就是该进程实际使用的内存大小
  • shared 表示进程和其他进程共享的内存大小

命令输出信息具体含义,跟 liunx 下内存管理相关,那个话题实在太大,这里只关注虚拟内存哪里来的这个问题。

上面的输出结果中,大头是下面这几列。一共 500+M + 64M * 5,大约 800+M

0000169de27ff000  520512 ----- 0000000000000000 000:00000   [ anon ]
00007f1630021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f1634021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f163c021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f1640021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007f1644021000   65404 ----- 0000000000000000 000:00000   [ anon ]
AI 代码解读

具体占用情况

作为一个程序员,基本一看就知道分两类,512M64M

下面这段话来自Java程序在Linux上运行虚拟内存耗用很大

我们知道Linux下glibc的内存管理机制用了一个很奇妙的东西,叫arena。在glibc分配内存的时候,大内存从从中央分配区分配,小内存则在线程创建时,从缓存区分配。为了解决分配内存的性能的问题,就引入了这个叫做arena的memory pool。而恰好,在64bit系统下面,它的缺省配置为64M。

Red Hat Enterprise Linux 6 features version 2.11 of glibc, providing many features and enhancements, including… An enhanced dynamic memory allocation (malloc) behaviour enabling higher scalability across many sockets and cores.This is achieved by assigning threads their own memory pools and by avoiding locking in some situations. The amount of additional memory used for the memory pools (if any) can be controlled using the environment variables MALLOC_ARENA_TEST and MALLOC_ARENA_MAX. MALLOC_ARENA_TEST specifies that a test for the number of cores is performed once the number of memory pools reaches this value. MALLOC_ARENA_MAX sets the maximum number of memory pools used, regardless of the number of cores.

验证一下:

$export MALLOC_ARENA_MAX=1
$./node
# 在另外一个窗口
$pmap -d 28567 |grep mapped
mapped: 666420K    writeable/private: 96472K    shared: 0K
AI 代码解读

64M的一块也没了。(994068 - 666420 = 327648)。

好了,就剩下最大的512M的问题了。

这块应该是 node 本身申请的了。在 node v8.x-staging 的代码上找了一下虚拟内存相关的代码。发现了这个VirtualMemory

按说这里只看代码就能对照调用链可以找到申请的地方。不过还是用了偷懒的办法:编一个debug版本的Node.js,然后利用gdb来找到backtrace。

断点设置在 v8::base::VirtualMemory::VirtualMemory(unsigned long, unsigned long, void*)

$./configure --debug
$make -j 4
make -C out BUILDTYPE=Release V=1
make -C out BUILDTYPE=Debug V=1
...
# 喝杯茶,出去转一圈回来就能编译好了
...

$ gdb out/Debug/node 
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from out/Debug/node...done.
(gdb) break v8::base::VirtualMemory::VirtualMemory(unsigned long, unsigned long, void*)
Breakpoint 1 at 0x26b6b90: file ../deps/v8/src/base/platform/platform-linux.cc, line 203.
(gdb) run
Starting program: /path/to/your/node/out/Debug/node 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff6b42700 (LWP 23574)]
[New Thread 0x7ffff6341700 (LWP 23575)]
[New Thread 0x7ffff5b40700 (LWP 23576)]
[New Thread 0x7ffff533f700 (LWP 23577)]

Thread 1 "node" hit Breakpoint 1, v8::base::VirtualMemory::VirtualMemory (this=0x7fffffffc780, size=536870912, alignment=4096, hint=0x137a187d000)
    at ../deps/v8/src/base/platform/platform-linux.cc:203
203        : address_(NULL), size_(0) {
(gdb) bt
#0  v8::base::VirtualMemory::VirtualMemory (this=0x7fffffffc780, size=536870912, alignment=4096, hint=0x137a187d000)
    at ../deps/v8/src/base/platform/platform-linux.cc:203
#1  0x000000000178cf0f in v8::internal::AlignedAllocVirtualMemory (size=536870912, alignment=4096, hint=0x137a187d000, result=0x7fffffffc810)
    at ../deps/v8/src/allocation.cc:117
#2  0x0000000001ea39e8 in v8::internal::CodeRange::SetUp (this=0x3ca6f20, requested=536870912) at ../deps/v8/src/heap/spaces.cc:122
#3  0x0000000001ea4649 in v8::internal::MemoryAllocator::SetUp (this=0x3ca7a00, capacity=1501560832, code_range_size=0)
    at ../deps/v8/src/heap/spaces.cc:304
#4  0x0000000001e22c46 in v8::internal::Heap::SetUp (this=0x3c65650) at ../deps/v8/src/heap/heap.cc:5922
#5  0x0000000001f5a8a7 in v8::internal::Isolate::Init (this=0x3c65630, des=0x7fffffffcc40) at ../deps/v8/src/isolate.cc:2786
#6  0x0000000002265697 in v8::internal::Snapshot::Initialize (isolate=0x3c65630) at ../deps/v8/src/snapshot/snapshot-common.cc:46
#7  0x00000000017d147d in v8::IsolateNewImpl (isolate=0x3c65630, params=...) at ../deps/v8/src/api.cc:8633
#8  0x00000000017d1284 in v8::Isolate::New (params=...) at ../deps/v8/src/api.cc:8580
#9  0x0000000002415900 in node::Start (event_loop=0x3a45fe0 <default_loop_struct>, argc=1, argv=0x3c63ea0, exec_argc=0, exec_argv=0x3c63f80)
    at ../src/node.cc:4856
#10 0x000000000240cba5 in node::Start (argc=1, argv=0x3c63ea0) at ../src/node.cc:4945
#11 0x00000000024843cb in main (argc=1, argv=0x7fffffffd8b8) at ../src/node_main.cc:106
(gdb) 
AI 代码解读

512M找到了:

Thread 1 "node" hit Breakpoint 1, v8::base::VirtualMemory::VirtualMemory (this=0x7fffffffc780, size=536870912, alignment=4096, hint=0x137a187d000)
AI 代码解读

顺着这条链路,我们可以看到有这么一段关于
CodeRange deps/v8/src/heap/spaces.h的描述:

// All heap objects containing executable code (code objects) must be allocated
// from a 2 GB range of memory, so that they can call each other using 32-bit
// displacements.  This happens automatically on 32-bit platforms, where 32-bit
// displacements cover the entire 4GB virtual address space.  On 64-bit
// platforms, we support this using the CodeRange object, which reserves and
// manages a range of virtual memory.
AI 代码解读

在 CodeRange::SetUp(size_t requested) 中的这段代码

undefined

  • 非常清晰的描述:

    // When a target requires the code range feature, we put all code objects
    // in a kMaximalCodeRangeSize range of virtual address space, so that
    // they can call each other with near calls.

kMaximalCodeRangeSize
512M的定义。

问题结论:

900+M的虚拟内存,其中512M是 v8 为代码申请。64M * N(根据OS设置)是glibc的一种内存分配机制产生。其它剩余小块没有深究,感兴趣的同学可以类似思路去探索。

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
穆客
+关注
目录
打赏
0
0
0
0
60
分享
相关文章
如何使用内存监控工具来定位和解决Node.js应用中的性能问题?
总之,利用内存监控工具结合代码分析和业务理解,能够逐步定位和解决 Node.js 应用中的性能问题,提高应用的运行效率和稳定性。需要耐心和细致地进行排查和优化,不断提升应用的性能表现。
246 77
选择适合自己的Node.js内存监控工具
选择合适的内存监控工具是优化 Node.js 应用内存使用的重要一步,它可以帮助你更好地了解内存状况,及时发现问题并采取措施,提高应用的性能和稳定性。
189 76
解读Node.js内存监控工具生成的报告
需要注意的是,不同的内存监控工具可能会有不同的报告格式和内容,具体的解读方法可能会有所差异。因此,在使用具体工具时,还需要参考其相关的文档和说明,以更好地理解和利用报告中的信息。通过深入解读内存监控报告,我们可以不断优化 Node.js 应用的内存使用,提高其性能和稳定性。
133 74
|
5月前
|
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
174 62
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
221 62
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
114 31
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
Node.js 是一种高效的 JavaScript 运行环境,基于 Chrome V8 引擎,支持在服务器端运行 JavaScript 代码。本文介绍如何在阿里云上一键部署 Node.js 环境,无需繁琐配置,轻松上手。前提条件包括 ECS 实例运行中且操作系统为 CentOS、Ubuntu 等。功能特点为一键安装和稳定性好,支持常用 LTS 版本。安装步骤简单:登录阿里云控制台,选择扩展程序管理页面,安装 Node.js 扩展,选择实例和版本,等待创建完成并验证安装成功。通过阿里云的公共扩展,初学者和经验丰富的开发者都能快速进入开发状态,开启高效开发之旅。
前端开发必备!Node.js 18.x LTS保姆级安装教程(附国内镜像源配置)
本文详细介绍了Node.js的安装与配置流程,涵盖环境准备、版本选择(推荐LTS版v18.x)、安装步骤(路径设置、组件选择)、环境验证(命令测试、镜像加速)及常见问题解决方法。同时推荐开发工具链,如VS Code、Yarn等,并提供常用全局包安装指南,帮助开发者快速搭建高效稳定的JavaScript开发环境。内容基于官方正版软件,确保合规性与安全性。
259 23
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
54 2
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!

热门文章

最新文章