JavaScript 的标准 ECMAScript 里没有对 GC 做相关的要求,因此 JavaScript 的 GC 机制完全由引擎决定:
一般存在三种垃圾回收的方法:
- stop-the-world: 它指的是在执行垃圾回收的过程中,会暂停程序的执行
- 增量式 GC(incremental),即程序不需要等到垃圾回收完全结束才能重新开始,在垃圾回收的过程中控制权可以根据情况临时交给程序执行
- 并发式 GC(concurrent),即在垃圾回收的同时不需要停止程序的运行,两者可以同时进行,只有在个别时候需要短暂停下来让垃圾回收器做一些特殊的操作
V8 内存管理
- 内存限制
受垃圾回收机制的限制(耗时长),V8的内存限制(64系统下为1.4GB,32系统下为0.7GB),注意的是Buffer存储在堆外存中,会不受v8内存分配的限制。在启动app时可以通过传递参数控制内存的大小:
node --max-old-space-size=1700 test.js // 单位为MB,设置老生代存储空间的大小
node --max-new-space-size=1024 test.js // 单位为KB,设置新生代存储空间的大小
- 内存指标
Node.js 提供了 process.memoryUsage/os.totalmem/os.freemem 函数来查看内存指标
process.memoryUsage()
=> {
rss: 22749184, // 常驻内存集的大小,就是给该进程分配了多少物理空间,包括堆,栈,代码段
heapTotal: 7708672, //分配的堆内存大小
heapUsed: 5078856, //堆已经使用的内存大小,内存泄漏主要查看该指标
external: 8614 // V8 引擎内部的 C++ 对象占用的内存
}
//返回操作系统的总内存大小
os.totalmem()
=> 17179869184
//返回操作系统的闲置内存大小
os.freemem()
=> 5585453056
V8的垃圾回收机制
v8垃圾回收算法:
V8 内存分为新生代区和老生代区,一般刚刚新建的对象都存放在新生代区,然后存活周期比较长的对象就会被放入老生代区,新生代区和老生代区都有各自的垃圾回收算法:
- Scavenge算法(新生代区):
采用复制的方式,将堆内存一分为二,通过将存活的对象在两个块内存之间复制实现,复制后重新整理内存,删除碎片。特点是由于生命周期短的对象只占少部分,时间效率比较高,但是由于只能使用堆内存一半的空间,空间利用率不高,适用于新生代对象;
- 对象晋升:
在一定的条件下需要将新生代中存活周期长的对象复制到老生代中,完成对象晋升,对象晋升的条件有两个:
- 对象是否经过多次Scavenge回收
- 新生代空间的内存占用比超过限制
- Mark-Sweep和Mark-Compact算法(老生代区):
老生代重要采用Mark-Sweep和Mark-Compact相结合进行垃圾回收:
- Mark-Sweep 先标记死的对象,然后清除死的对象,会产生碎片。
- Mark-Compact 会在标记死亡对象后,对内存进行整理,无内存碎片产生,速度相对比较慢
V8 的老生代区主要使用 Mark-Sweep,在空间不足的情况下才使用 Mark-Compact 算法。并且使用增量标记回收(Incremental Marking):由于老生代一次完全的垃圾回收耗时长,如果完全停顿下来进行垃圾回收,回收完以后再执行应用逻辑,性能会很差,所以采用 Incremental Marking 方式进行回收,采用增量标记,每做完一个步进,执行一会应用逻辑,提高性能;
查看垃圾回收日志:
- –trace_gc参数:启动时添加该参数,可以查看 node 日志回收信息:
node --trace_gc app.js