Node的内存控制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node的内存控制相关的知识,希望对你有一定的参考价值。

参考技术A

如果您看到上面的错误,这意味着您的 NodeJS 应用程序内存不足,它消耗的内存超过了分配的内存,最终导致它自行终止。

当应用程序批处理大量数据时,数据处理算法的编写方式使其需要保留堆空间中的对象,直到处理完成。随着处理的进行,应用程序逐渐使用了更多内存,V8也将 将花费更多时间进行垃圾收集以释放未使用的内存,直到最终达到分配给进程的限制并导致了OOM。

Node.js 运行时在内存使的用方面非常高效,因此程序通常使用默认限制运行良好。并且,如果没有主动设置最大堆大小,程序则会使用默认内存限制,并且此默认值也是会根据 Node.js 版本和程序运行的系统架构而有所不同。

下面我们具体了解一下:

javascript与Java一样,由垃圾回收机制来进行自动的内存管理。对于性能敏感的服务器端程序,内存管理的好坏、垃圾回收状况是否优良,都会对服务构成影响。而在Node中,这一切与V8引擎息息相关。

网上大都说,Node中通过JavaScript只能使用部分内存(64位约1.4G,32位约0.7G)。V8对内存做了限制。因此这种限制下,将会导致Node无法直接操作大内存对象。但是随着版本升级,这个数据好像不是那么绝对。

关于限制官方也没直接说明(主要不确定是否能通过buffer.constants.MAX_LENGTH直接类比),所以写个小程序大概在64位系统上跑一下。

Node.js (64位实测)版本限制

官方文档buffer.constants.MAX_LENGTH

为了解决 OOM 错误,您需要做的就是显式配置内存限制使用 Node.js 命令行选项

Javascript:

Typescript的 ts-node :

这就能快速解决 Node.js 内存不足的问题!

建议始终明确设置, --max-old-space-size 而不是依赖 Node.js 的默认值,因为在较新的 Node.js 版本中默认值可能会更改。

在具有 2 GB 内存的机器上,考虑将其设置为 1536 (1.5 GB) 以留出一些内存用于其他用途并避免内存交换。
如果您在小型机器(例如 Raspberry Pi 板)上使用 Node.js 运行简单的 Web 服务器,您可以将 设置 --max-old-space-size 为适当的小值,例如 128 MB,以避免 Node.js 占用过多宝贵的内存。

关于pm2的具体使用请查看我的文章 Node服务与pm2实战

通过我们除了前端项目编译(各种cli等等)可能出现内存不足,node服务端也可能导致此问题。前端编译我们很简单的借助增加默认内存可以解决,但是服务端部署是一个持续过程,我们很少使用node直接启动的方式启动服务。我们通常借助 pm2 工具来进行,它可以在服务因异常或其他原因被杀掉后进行自动重启。 由于Node的单线程特征,自动重启能很大程度上的提高它的健壮性。

因为我们服务端使用pm2的目的之一,是服务出问题自动重启,而万一我们设置的内存不足或者服务考虑不足有些问题,导致服务内存不足崩溃对于生产环境来说很不友好。而 pm2 针对内存不足也有一个重启命令,一旦内存不足,会自动重启服务,防止整个服务卡死。

当内存超过1024M时自动重启。 如果工程中有比较棘手的内存泄露问题,这个算是一个折中方案。

pm2其实也是支持配置文件来启动的,我们也可以借助配置文件来配置命令与参数:

东哥学Node的故事——内存管理

前言

东哥是一个平凡的前端攻城狮,北邮网研院研二在读,刚接触Node不久,心里充满了对Node的好奇和崇拜,只听噗通一声,掉入了Node的坑。。。

于是东哥开始疯狂地看Node相关的书籍,这不,就学到了Node.js内存管理这一章。

他读到:“对于那些短时间执行的场景,比如网页应用、命令行工具,内存的管理似乎没有太大的必要。因为运行时间短,随着进程的退出,内存得到释放,几乎没有内存泄露,即使存在内存使用过多的情况,也只会影响到终端用户。所以,我们在使用JavaScript进行前端开发的过程中,很少会考虑内存管理的问题”。

东哥觉得很有道理,产生了共鸣:“是啊,干了这么久前端,写了这么多网页,还真没考虑过内存问题!”

顺便提一句,东哥还是一个算法狂人,曾经使用Java这门走遍天下的语言刷遍了各大平台(Leetcode、剑指offer、牛客网。。。)的算法题,可谓十分强势!

东哥很聪明,心想:既然Node.js是一个针对服务器端开发的平台,也应该和Java一样存在一些诸如内存泄露、内存分配优化等问题吧。

他读到:“随着Node.js在服务器端的广泛应用,其他语言在内存管理上存在的问题在JavaScript中也暴露了出来。”

心想:“有道理,让俺一睹其究竟!”

 

V8垃圾回收机制与内存限制

Node与V8

2009年,Node的创始人Ryan Dahl选择了V8来作为Node的JavaScript脚本引擎,在第三次浏览器大战中,Google的Chrome浏览器凭借V8的优异性能成为焦点。

V8内存限制

在一般的后端开发语言中,在基本的内存使用上没有什么限制,然而在Node中通过JavaScript使用内存是就会发现只能使用部分内存(64位系统下约为1.4GB,32位系统下约为0.7GB)。

最近,东哥刚入手了一台32GB内存的服务器用于大数据分析处理,有一天,他试图将一个2GB的文件读入内存中进行字符串分析处理,这岂不是小菜一碟?可是。。。东哥失败了。。。

造成这个问题的主要原因在于Node是基于V8构建的,所以在Node中使用的JavaScript对象基本上都是通过V8自己的方式进行管理分配的。V8的这套内存管理机制对于浏览器端使用起来可谓绰绰有余,但在服务器端却大大限制了开发者随心所欲地使用大内存的想法。

V8对象分配

在V8中,所有的JavaScript对象都是通过堆来进行分配的。V8堆示意图如下:

 

 可以使用process.memoryUsage()来查看内存使用量:

其中,rss是resident set size的缩写,即进程的常驻内存部分。进程的内存总共有几部分,一部分是rss,其余部分在交换区(swap)或者文件系统(filesystem)中;除了rss外,heapTotal和heapUsed对应V8堆内存的信息,heapTotal是堆中总共申请的内存空间,heapUsed是目前堆中使用的内存空间。

除此以外,还可以使用os模块的totalmem()和freemem()两个方法查看操作系统的内存使用情况,它们分别返回的是系统的总内存和闲置内存,以字节为单位:

可见,我这台屌丝机系统总内存为4GB,当前闲置内存大致为2.8GB。

东哥在熟悉了查看内存信息的方法后一直很纳闷,V8为什么要限制堆的大小呢,这样做是不是会让Node内存使用性能变得很低下?

表层原因是因为V8最初是为浏览器而设计的,不太可能有用到大量内存的情况。而深层原因是V8的垃圾回收机制的限制。

当然,我们也可以自行配置内存使用空间,因为V8也给开发者提供了选项让我们使用更多的内存。示例如下:

node --max-old-space-size=1700 test.js //单位为MB
node --max-new-space-size=1700 test.js //单位为KB  

V8垃圾回收机制

V8的垃圾回收策略主要是基于分代式垃圾回收机制。在V8中,主要将内存分为新生代和老生代。新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长的或常驻内存的对象。

涉及到的垃圾回收算法算法主要有三种:

关于算法的细节,由于时间有限,就不在这里详细描述了,大家可以对比着看看各种算法的特点。

如何查看垃圾回收日志?

查看垃圾回收日志的方式主要是在启动时添加--trace_gc参数。将会在gc.log文件中得到所有的垃圾回收信息:

gc.log文件大概长这个样:

通过查看gc.log文件,我们可以找出回收哪些阶段比较耗时,触发的原因是什么。

另外,通过在Node启动时使用--prof参数,可以得到V8执行时的性能分析数据,其中包含了垃圾回收执行时占用的时间。我在本地写了一个test.js文件:

for(var i=0;i<1000000;i++){
    var a = {};
}

执行如下命令:

会在目录下得到一个v8.log日志文件,长这样:

显然,该文件不具备可读性。。。所幸,V8提供了linux-tick-processor工具用于统计日志信息。我们执行:

就能得到统计结果,大致如下:

 

统计内容较多。其中,垃圾回收部分如下:

由于不断分配对象,垃圾回收所占的时间为5.4%.按此比例,时间循环执行1000毫秒的过程中要给出54毫秒用于垃圾回收。

 

高效使用内存

作用域(链)

在JavaScript中能形成作用域的有函数调用、with语句以及全局作用域。以如下代码为例:

var foo = function(){
   var local = {}; 
};

foo()函数在每次被调用时会创建对应的作用域,函数执行结束后,该作用域将会销毁。同时作用域中申明的局部变量随着作用域的销毁而销毁,局部变量local失效,其引用的对象将会在下次垃圾回收时被释放。

而作用域链是指JavaScript执行过程中变量的查找会沿着一层一层的作用域形成的链进行,一直到全局作用域。

所以,主动释放变量可以合理地利用作用域(链)原理,让我们高效地使用内存。

闭包

闭包大家再熟悉不过了,在JavaScript中,实现外部作用域访问内部作用域中的变量的方法叫做闭包。而闭包的存在,会使得局部变量常驻内存,不能被及时释放回收。

所以,闭包要慎用。即使使用,也要在适当的时候主动释放局部变量。

内存泄露

内存泄露的原因主要有如下几个:

1、缓存

2、队列消费不及时

3、作用域未及时释放

 

所以,预防措施主要有如下几个:

1、慎将内存当做缓存使用

2、关注队列状态

3、及时释放作用域中的对象和变量

 

内存泄露排查

 推荐几个排查工具,可通过npm安装使用:

1、v8-profiler

2、node-heapdump

3、node-mtrace

4、dtrace

5、node-memwatch

 

以上是关于Node的内存控制的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出Node.js - 内存控制

Node.js——nodejs(内存控制)(转)

内存控制1

读书笔记《深入浅出nodejs》第五章 内存控制

Node.js的内存分配和垃圾回收

Websocket 内存泄漏 node.js。事件发射器?