简单了解V8的垃圾回收机制

896 阅读5分钟

前言

在使用Node.js中发现有时内存动不动就爆炸,知道看到《深入浅出Node.js》里谈论到V8的内存限制和垃圾回收算法,我才恍然大悟,现简单记录一下吧。

V8的内存限制

书中说到对于1.5GB的堆内存,V8做一次小的垃圾回收就需要50毫秒,一次非增量的垃圾回收就需要1s,而且垃圾回收是全暂停的方式,也就是说,你的程序运行进程将会暂停转而去垃圾回收(且Node是单线程的),这就很难受了。更难受的是64位的系统,JS只能使用大约1.4GB的内存,32位的系统大约0.7GB。 打开内存限制的可以使用下面两个参数

node --max-old-space-size=1700 test.js  #单位MB  设置老生代内存
node --max-new-space-size=1024 test.js  #单位KB  设置新生代内存

V8主要的垃圾回收算法

先来看看具体场景下打印垃圾回收日志的命令是什么

node test.js --trace_gc >gc.log   #输出到日志,否则输出到控制台

好了,V8的整体使用分代式垃圾回收机制

根据对象的生存长短分为新生代老生代两代。

在新生代中,采用Scavenge算法,一分为二,每一部分空间都是semispace,一个在使用中(From),一个处于空闲状态(TO),垃圾回收将From中活着的对象复制到To,两者的角色相互转换(翻转)。

多次复制依然活着的对象(生命周期)就转移到老生代的空间中,这叫做晋升。

PNG图像

PNG图像 2

在老生代的中使用Mark-Sweep和Mark-Compact算法结合,

Mark-Sweep标记所有活着的对象,清除未标记的(死去的对象),结果如下图

Mark-sweep

该算法由于会造成空间碎片,浪费空间(不连续的内存区域),因此又引入了Mark-Compact算法,将活着的对象往一端移动。

下图中白色格子是活着对象,深色死去的,浅色嘛,是移动后的空间(原来的位置)

PNG图像 2

具体算法描述参考图书中的内容。

我在输出垃圾回收日志中看到有关信息

var a=[];
​
for(var i=0;i<1000000;i++){
    a.push(new Array(1000))
    var b=12;
    delete b;
}
node app.js --trace_gc >gc.log
​
[4188:000001F722B22570]       37 ms: Scavenge 3.8 (4.0) -> 3.7 (5.0) MB, 1.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       38 ms: Scavenge 3.8 (5.0) -> 3.6 (6.3) MB, 0.9 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       38 ms: Scavenge 4.6 (6.3) -> 4.7 (8.3) MB, 0.5 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       40 ms: Scavenge 5.6 (8.3) -> 5.6 (9.0) MB, 1.0 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       42 ms: Scavenge 6.5 (9.0) -> 6.5 (13.8) MB, 1.3 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       44 ms: Scavenge 9.4 (13.8) -> 9.6 (14.8) MB, 1.5 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       47 ms: Scavenge 10.3 (14.8) -> 9.9 (25.8) MB, 2.5 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       52 ms: Scavenge 17.0 (25.8) -> 17.7 (26.5) MB, 2.5 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       56 ms: Scavenge 17.7 (26.5) -> 16.7 (49.5) MB, 4.0 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       65 ms: Scavenge 32.5 (49.5) -> 34.0 (51.0) MB, 4.5 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       71 ms: Scavenge 34.0 (51.0) -> 32.0 (65.8) MB, 5.2 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       76 ms: Scavenge 47.7 (65.8) -> 49.2 (66.8) MB, 4.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       82 ms: Scavenge 49.2 (66.8) -> 47.2 (80.8) MB, 5.2 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       87 ms: Scavenge 62.9 (80.8) -> 64.4 (82.0) MB, 4.0 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]       93 ms: Scavenge 64.4 (82.0) -> 62.4 (96.0) MB, 5.3 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[4188:000001F722B22570]      210 ms: Mark-sweep 134.6 (166.2) -> 133.7 (169.2) MB, 75.9 / 0.0 ms  (+ 2.3 ms in 9 steps since start of marking, biggest step 0.7 ms, walltime since start of marking 80 ms) (average mu = 1.000, current mu = 1.000) finalize incremental marking via stack guard GC in old space requested
[4188:000001F722B22570]      751 ms: Mark-sweep 554.1 (597.2) -> 553.3 (596.4) MB, 320.5 / 0.0 ms  (+ 2.1 ms in 9 steps since start of marking, biggest step 0.7 ms, walltime since start of marking 326 ms) (average mu = 0.404, current mu = 0.404) finalize incremental marking via stack guard GC in old space requested
[4188:000001F722B22570]     1944 ms: Mark-sweep 1305.2 (1361.8) -> 1303.8 (1360.3) MB, 766.3 / 0.0 ms  (+ 2.2 ms in 9 steps since start of marking, biggest step 0.7 ms, walltime since start of marking 774 ms) (average mu = 0.371, current mu = 0.356) finalize incremental marking via stack guard GC in old space requested
[4188:000001F722B22570]     3164 ms: Mark-sweep 1680.2 (1743.6) -> 1678.8 (1742.2) MB, 1010.3 / 0.0 ms  (+ 2.0 ms in 9 steps since start of marking, biggest step 0.7 ms, walltime since start of marking 1018 ms) (average mu = 0.254, current mu = 0.171) finalize incremental marking via stack guard GC in old space requested
[4188:000001F722B22570]     4538 ms: Mark-sweep (reduce) 1850.0 (1916.5) -> 1850.0 (1885.5) MB, 1254.3 / 0.0 ms  (+ 3.3 ms in 9 steps since start of marking, biggest step 1.2 ms, walltime since start of marking 1265 ms) (average mu = 0.158, current mu = 0.085) finalize incremental marking via stack guard GC in old space requested
[4188:000001F722B22570]     5914 ms: Mark-sweep (reduce) 1949.0 (1986.2) -> 1949.0 (1986.2) MB, 1285.7 / 0.0 ms  (+ 0.2 ms in 3 steps since start of marking, biggest step 0.1 ms, walltime since start of marking 1292 ms) (average m

可以看到垃圾回收日志中的确有Scavenge和Mark_sweep的名字,且可以卡到具体的垃圾回收的用时。

刚开始学习Node,不妨多看看垃圾回收日志,看看代码里哪段的垃圾回收时间过长,如何去规避这些问题,写出高性能的代码,我想,这是对当下一上手就大项目一上线就漏洞百出层层报错的最好解决方法。

关于V8的垃圾回收机制先到此结束,以后了解更多后再来补充,详细的部分还是推荐细看《深入浅出Node.js》