前言
Node.js 默认使用的是 ptmalloc(glibc)
内存分配器,而:
在服务端领域「不会选择默认的 malloc」是一个常识。( 来源 )
ptmalloc
的分配效率较低( 来源 ),对于 长时间、多核 / 多线程 运行的程序( 来源 ),特别适合使用 jemalloc
分配器,使用 jemalloc
分配内存可以更好的处理 内存碎片 问题( 来源 )。
jemalloc
并不总是最优的( 来源 ),但十分适合服务端 Node.js 程序使用。
正文
Linux
# install
sudo apt-get update
sudo apt-get install -y libjemalloc-dev
# run
# 启动的时候添加 jemalloc 分配器,`LD_PRELOAD` 的格式是以空格为分割。
LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libjemalloc.so $LD_PRELOAD" node index.js
将 jemalloc
写入 ld.so.preload
可以让所有写入之后运行的进程默认使用 jemalloc
分配器,一般没必要,除非你有会启动多个进程的场景:
echo "/usr/lib/x86_64-linux-gnu/libjemalloc.so" >> /etc/ld.so.preload
检验对应的 node 进程是否正确使用了 jemalloc
:
# 使用 top 等工具找到对应 node 进程的 pid 再查询即可
sudo cat /proc/<PID OF NODEJS>/smaps | grep jemalloc
macOS
# install
brew install jemalloc
# run
# 在具体使用时 `${version}` 版本号的部分换成你自己安装的即可
DYLD_INSERT_LIBRARIES=/usr/local/Cellar/jemalloc/5.3.0/lib/libjemalloc.dylib node index.js
# ^ ${version}
检验脚本
运行以下脚本可以测试 jemalloc
是否生效,默认情况下会使用多达 4 G
内存,若使用 jemalloc
,则使用内存会大大减少至几十 M 。
注:不要在内存不足 8 G
的服务器上运行,剩余内存不够会直接宕机。
// index.js
const bs = 4 * 1024 * 1024; // 4 MiB
const retained = [];
let i = 0, flag = false;
function tick() {
i++;
if (i % 1000 === 0) {
console.log(`RSS [${i}]: ${process.memoryUsage().rss / 1024 / 1024} MiB`);
}
retained.push(Buffer.allocUnsafe(bs));
if (i === 5000) {
console.log('Clearing retained and enabling alloc');
retained.length = 0;
flag = true;
}
if (flag) Buffer.alloc(bs); // Buffer.alloc(bs - 10) seems to be fine here
if (i < 10000) setImmediate(tick);
}
tick();
例子
个人例子:使用 jemalloc
后,在我的服务端程序上原持续占用 900 M
内存的程序,现会在 200 M
至 900 M
之间波动,可以看到回收效率显著提高,在调用需求下降时(如半夜),内存使用大幅减少:
经典例子:npmmirror 使用 jemalloc
大幅减少占用内存。( 来源 )
总结
对于 长时间、多进程 的 Node.js 服务端程序,我们可以切换至 jemalloc
内存分配器提高内存回收效率,特别对于易产生内存碎片(如大量 JSON
序列化)的场景,格外有效。
实践中,修订容器脚本,在镜像构建时运行对应的命令即可。