这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
这是内存控制章节的第五篇文章,内存泄漏
- 第一篇:V8 的内存限制和原因
- 第二篇:V8 的垃圾回收机制
- 第三篇:高效使用内存
- 第四篇:内存指标
前面四篇文章讲到,
由于 V8 垃圾回收机制的限制,nodejs 在使用内存时有一些限制,只能使用部分内存(64位系统下约为1.4 GB,32位系统下约为0.7 GB)
浅析了一个 V8 垃圾回收机制中,对于老生代和新生代的回收算法。
为了让 V8 垃圾回收机制更好的工作,我们需要注意全局变量和闭包的使用,因为它们会导致内存无法立即回收,让老生代中对象增多。
我们可以通过 os 下的 totalmem 和 freemem 查看系统的内存使用情况,通过 process.memoryUsage() 查看进程的内存使用情况,nodejs 进程内存中,有V8申请的堆内存和 Buffer 使用的内存部分,Buffer 使用的内存不受堆内存大小限制
这篇文章主要讲内存泄漏的原因和解决方式:
在 V8 的垃圾回收机制下,很少会出现内存泄漏的问题。但是 nodejs 对内存泄漏十分敏感,一旦出现内存泄漏,垃圾回收时要耗费更多的时间进行扫描,会使应用响应缓慢,直到进程内存溢出,应用崩溃。
内存泄漏的原因很多,但是本质是应该被回收的对象出了意外没有回收,变成了常驻老生代的对象。
造成内存泄漏的原因可能有如下几个,我们分别看一下:
- 缓存
- 队列消费不及时
- 作用域未释放
谨慎把内存当作缓存
缓存限制策略
如果想通过 JavaScript 的对象缓存一些结果,再下次访问的时候优先判断缓存中的内容,如果没有再去获取。这种写法是可以的,但是要谨慎,防止内存泄漏的问题。
为什么这种写法可能产生内存泄漏的问题呢?
因为一旦这个对象被当作缓存使用,它会常驻在老生代中,而对象的键越多,占用的内存空间越大,垃圾回收时会对这些对象做无用的处理,且会影响内存用量。
如果要使用这种方式的话,需要对缓存进行限制,比如把最久未使用的缓存淘汰。可以使用Isaac Z. Schlueter采用LRU算法的缓存,地址为 github.com/isaacs/node… ,合理的使用缓存。
另外在使用模块时,因为模块都会被编译执行,然后缓存到老生代。所以在设计模块时,要小心内存泄漏的出现
比如这个例子,leakArray 随着 leak 的执行不断增加,导致局部变量 leakArray 不断增加内存占用,不被释放
var leakArray = [];
exports.leak = function () {
leakArray.push("leak" + Math.random());
};
这种情况需要添加清空队列的接口,供调用者释放内存。
缓存的解决方案
使用缓存可以尽量使用进程外的缓存,外部的缓存软件有良好的过期策略和自有的内存管理
外部缓存主要使用 Redis 和 Memcatched
他们主要的好处是:
- 将缓存转移到外部,减少常驻内存进程的数量,让垃圾回收更高效
- 进程之间可以共享缓存
关注队列状态
在生产者-消费者模型中,我们会使用队列(数组对象)作为中间产物,这种情况下,一旦消费的速度小于生产的速度,就会形成堆积。
比如我们在收集日志时,如果缺少考虑,可能会使用数据库来存储日志。日志通常是海量的,数据库是构建在文件系统之上的,数据库的写入速度远小于文件系统,于是会形成数据库写入操作的堆积。内存占用增加,造成内存泄漏。
这种场景,表层的解决方式是,我们可以把日志写入到文件中,但是生产速度如果因为某些原因激增,还是会有问题的。
深度的解决方式是监控队列的长度,如果有问题应该产生报警。另一个是任何一个异步调用都应该有超时机制,调用加入队列时就开始计时,一旦没有在规定时间内完成响应,应该通过回调函数传递超时异常。或者可以采用拒绝模式,当队列拥塞时,新到来的调用直接响应拥塞错误。
以上为内存泄漏的原因和解决方式,欢迎点赞和评论~