node使用V8作为javaScript脚本引擎
v8的内存限制和对象分配
限制:64为大约1.4G,32位大约0.7G
v8中所有javascript对象都是通过堆内存进行分配的。内存查看命令process.memoryUsage()
为何要内存限制
表层原因为v8最初为浏览器设计,不太可能遇到大量的内存的场景。对于网页来说,v8的限制已经绰绰有余,深层原因是v8的垃圾回收机制的限制.
v8打开堆内存的限制命令 --max-old-space-size或--max-new-space-size
v8的垃圾回收机制
v8的内存分代
主要将内存分为新生代和老生代。新生代中为存活时间交短的对象,老生代中的对象为存活时间较长或者常驻内存对象
- 新生代内存在64位系统中为16MB,32位中为8MB
- v8堆内存最大保留空间 4 * 新生内存 + 老生代内存
新生代内存垃圾回收
采用一种复制方式的垃圾回收算法,将堆内存一分为二,只有一部分空间被使用称为From空间,另一个处于闲置称为To空间。当进行分配对象的时候先在from空间分配,当进行垃圾回收时,会检查from空间中的存活对象,将这些存活对象复制到to空间中,复制完成后From和to空间角色互换,清空to空间,在垃圾回收过程中就是通过将存活对象在两个空间中进行复制。
- 缺点: 只能使用一半的内存
- 优点: 只复制存活的对象,对于生命周期短的场景存活对象只占小部分,所以时间效率高
当一个对象经过多次复制依然存活时,就会被认为是生命周期较长的对象,会被移入老生代内存中。
对于移入老生代内存有两个条件: - 对象已经经过新生代内存回收机制的回收依然存活
- 复制到To空间的对象超过25%(为什么是25%?这个To空间接下来会成为From空间并接受内存分配,如果占比过高影响后续分配)
老生代内存垃圾回收
采用标记清除,它分为标记清除两个阶段
在标记阶段遍历所有的对象并标记活着的对象,在清除阶段只清除死亡的对象,死亡对象在老生代内存只占一小部分。老生代内存进行一次清除后,内存空间会出现不连续的状态,所以清理完成需要进行一步标记整理。
为了避免出现javaScript应用逻辑与垃圾回收器看到不一致的情况,垃圾回收都要将应用逻辑停下来,这种行为会造成停顿,在新生代垃圾回收过程中因为存活对象比较少,即使停顿基本影响不大。在老生代垃圾回收中,通常存活对象较多,全堆垃圾回收的标记、清除、整理影响较大。
解决办法:分批次进行,拆分成许多小步,每进行一小步就让逻辑运行一会
node 查看垃圾回收日志 运行时加入 参数 --trace_gc
高效使用内存
作用域
减少使用全局作用域,在局部作用域声明变量。当函数执行完成,该作用域就会被销毁,只别局部变量引用的对象存活较短,会被分配到新生代内存,方便回收
闭包
function test() {
var a = 1
return function () {
return a
}
}
var fn1 = test()
堆外内存
通过命令process.memoryUsage() 可以查看代rss总是大于常驻内存总量,在node中并不是所有的内存都通过v8进行分配,不通过v8进行分配的内存称为堆外内存,通过Buffer 分配的内存即为堆外内存,所有处理大量数据的时候可以使用Buffer 进行分配
内存泄漏
通常造成内存泄漏的原因有:
- 缓存
- 队列消费不及时
- 作用域未释放
慎用内存当缓存
缓存的访问效率要比I/O的效率高很多,一旦命中缓存,就可以节省一次I/O的时间,但是在node中,一旦一个对象被当作缓存,那它将常驻老生代内存,缓存中储存的键越多,长期存活的对象就越多,这将导致垃圾回收在进行扫描和整理是频繁的多这些对象做无用功。