2020-08-28-Node内存控制
V8内存
V8内部的内存对象分为新生代和老生代,新生代是代表存在时间较短,很快被释放内存空间的对象,而老生代对象则是常驻在Node进程中,
只有进程结束才会被销毁。
一般来说V8对新老生代对象的内存分配是固定的,根据设备的64位(old: 1400MB,new: 4*16MB)或者32位(减半)分配对应的内存。
但是我们也可以在node进程启动的时候主动去分配。
node --max-old-space-size=1700 test.js // 单位为MB
node --max-new-space-size=1024 test.js // 单位为KB
垃圾回收机制:
分代式垃圾回收
-
scavenge算法(复制剔除大法)
主要针对新生代对象的垃圾回收,它会将新生代分配的内存拆分为两份(from-to),新晋的新生代对象分配到
from
区,当进行垃圾回收的
时候,会将from区的对象复制到to
区,在这个过程中,判断出可以销毁的变量对象,直接释放对应的内存,然后再将to区的对象还原到from区,
等待下次回收。这个算法相当于只能利用一半的内存,但是速度非常快,典型的空间换时间,空间还是比较宝贵的,所以
scavenge算法只适合用于新生代小对象的回收。 -
晋升
在经历过scavenge处理多次后,仍然处于存活状态的对象,就会被移动到老生代中,采用新的算法进行管理。
触发条件:
1、经过scavenge回收;
根据对象的内存地址来判断是否进行过scavenge回收,有,则进入老生代空间。
2、To空间内存占用超过限制(新生代内存分配较少)。
在复制到To空间的过程中判断To空间的使用率是否超过
25%
,是,则进入老生代空间。
之所以限制25%,因为回收完毕后,To空间会与From空间进行互换,如果占比过高,会影响From空间的新的内存分配。 -
mark-sweep算法(标记清除大法)
对于老生代对象,大部分对象的存活期是比较长的,频繁的进行复制操作,非常耗时,所以采用mark-sweep算法来进行老生代对象的回收,
它遍历整个堆中的对象,标记出存活和死亡的对象,然后将死亡的对象内存空间直接释放。该算法的问题是回收完毕后,会出现内存空间不连续,产生内存碎片,影响后续大对象的内存分配。
-
mark-compact(标记清除再聚拢)
mark-sweep的衍生版本,在清除死对象的时候,将存活对象向一端移动,避免产生内存碎片。
算法 scavenge mark-sweep mark-compact
---------------------------------------------------------
空间开销 双倍空间(无碎片) 少(有碎片) 少(无碎片)
速度 极快 中等 最慢
是否移动对象 是 否 是
内存溢出
通常来说内存溢出的原因有:
- 作用域未被及时释放(闭包、过多的全局变量等)
- 缓存(未设置过期时间)
- 队列消费不及时(定时器或异步操作导致事件队列堆积了过多的任务)
大文件操作
由于V8内部对内存大小有限制,所以在读取一些超大文件时,我们无法通过fs模块,而需要使用Stream流,它分为可读、可写流,
含有pipe方法,对文件流进行操作,不会受到内存的影响。
var reader = fs.createReadStream('a.txt');
var writer = fs.createWriteStream('b.txt');
reader.pipe(writer);