在一般的后端语言中,基本的内存使用上基本没有什么限制,但是在nodeJs中却只能使用部分内存。在64位系统下位约为1.4G,在32位系统下约为0.7G,造成这个问题的主要原因是因为nodeJs基于V8构建,V8使用自己的方式来管理和分配内存,这一套管理方式在浏览器端使用绰绰有余,但是在nodeJs中这却限制了开发者,在应用中如果碰到了这个限制,就会造成进程退出。
戴着脚铐跳舞
在服务端,假如将一个2G的文本文件读到内存中,node进程就会进程崩溃,虽然这种情况不常见,但是开发时候也只能小心翼翼,该如何解决呢?
暴力加内存
在启动node进程的时候,可以调整内存大小。
node --max-old-space-size=1700 test.js // 单位为MB
node --max-new-space-size=1024 test.js // 单位为KB
这个在初始化进程的时候就生效,而且不能动态扩容,一般用来扩充内存,以免稍微多一些内存就崩溃。
合理利用内存
V8限制内存用量,主要是因为V8垃圾回收机制的设计。也正是因为垃圾回收机制,我们才不需要像c++那样手动回收。
在V8中,JavaScript的对象是分配在堆内存中,使用process.memoryUsage()
可以查看当前内存使用状态。现在内存有了最大的限制,那么我们写代码的时候就要额外注意了,要避免滥用内存。
1.及时释放全局变量
global.a = {name: 'a object'};
console.log(global.a); // 全局变量不会再用到
global.a = undefined; //释放该对象
- 合理使用闭包,及时释放闭包函数的引用。
在启动node进程时带上--trace_gc
会在V8进行垃圾回收的时候打印日志,可以帮助我们观察程序内部垃圾回收的情况。
内存泄漏
内存泄漏的实质就是应该被垃圾回收的对象出现意外,没有被回收,变成常驻于内存中的对象。
通常造成内存泄漏的原因有如下几个:
使用内存进行缓存
队列消费不及时
作用域未释放
谨慎使用缓存
缓存和之前说的全局变量其实是一回事,平时前端JavaScript使用缓存的场景可能是因为api请求慢,于是声明一个全局变量来缓存数据,如果有缓存就不用再发请求了,节约了时间。但是在nodeJs中,缓存并非物美价廉。
lodash的memoize方法,提供了一种缓存策略,利用一个map的键值对来缓存数据
function memoize(func, resolver) {
if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) {
throw new TypeError('Expected a function')
}
const memoized = function(...args) {
const key = resolver ? resolver.apply(this, args) : args[0]
const cache = memoized.cache
if (cache.has(key)) {
return cache.get(key)
}
const result = func.apply(this, args)
memoized.cache = cache.set(key, result) || cache
return result
}
memoized.cache = new (memoize.Cache || MapCache)
return memoized
}
这种方法如果不做限制,或导致缓存对象过大,一直占用在内存中,使用需谨慎。
关注队列
node底层的事件循环,会不停查看是否有事件待处理,如果有的话,就取出事件以及相关回调函数,如果有回调函数则执行。
事件循环一个典型的生产者/消费者的形式,异步I/O是事件的生产者,事件循环就是消费者。大部分时候消费速度都是大于生产速度的,但当生产速度大于消费速度的时候,事件会产生堆积,回调函数中的作用域不会得到释放,于是产生了内存泄漏。
例如现在往数据库插入100W条数据,数据库建立在文件系统之上,写数据效率很慢,很容易产生过多事件等待处理,这时候内存泄漏就无法避免了。
解决方案:
监控队列长度
为异步请求加上超时机制。