node 垃圾回收机制

之前看深入浅出nodejs时读到过node的垃圾回收机制,后来渐渐忘了,这次有时间突然想起,再次复习了一下;

除堆外内存,其余部分均由V8管理。

  • 栈(Stack)的分配与回收非常直接,当程序离开某作用域后,其栈指针下移(回退),整个作用域的局部变量都会出栈,内存收回。
  • 最复杂的部分是堆(Heap)的管理,V8使用垃圾回收机制进行堆的内存管理,也是开发中可能造成内存泄漏的部分

【默认情况下,V8为堆分配的内存不超过1.4G:64位系统1.4G,32位则仅分配0.7G。也就是说,如果你想使用Node程序读一个2G的文件到内存,在默认的V8配置下,是无法实现的。不过我们可以通过Node的启动命令更改V8为堆设置的内存上限】

node的垃圾回收有两块:新生代和老生代,它们各自使用不同的清理算法;

先来说新生代,顾名思义,新生代中的对象一般存活时间较短,它采用的是Cheney算法;

新生代的内存被一分为二,这两块小地盘都叫semispace空间,这俩小地盘只有一个处于使用中,另一个处于闲置状态,而处于使用中的叫From空间, 闲置的叫To空间;

当我们在代码里声明对象的时候,这个对象就会被安置到From空间;那个To空间就是闲置的;但当开始垃圾回收时,算法会检查From空间“还活着”的对象,把还活着的对象复制到To空间,然后把From空间内容清除掉成为To空间,在复制过程中会检查对象的内存地址来判断这个对象是否已经经历过一次Scavenge回收,如果这个对象已经经历过一次了,这个对象就会被放到老生代里或者如果To空间的使用量已经到达25%,这个对象也会直接升入老生代;这之后To空间里存放该轮垃圾回收后存活的对象,然后这个To空间就会改名From空间,后续如果又有新变量声明了就继续存放在这个From空间,之前那个From空间则会成为To空间;新生代的垃圾回收就是这样复制来复制去;也许有人会觉得复制过程很费时间,统计学指导,新生代中大多数对象寿命都不长,长期存活对象少,则需要复制的对象相对来说很少,因此总体来说,新生代使用Scavenge算法的效率非常高。且由于Scavenge是依次连续复制,所以To空间永远不会存在内存碎片。

上面说到新生代升入老生代的两种情况:1. 经历过一次Scavenge回收的;2.在复制过程中,To空间已经使用超过25%;

至于为什么是25%,在复制结束后,To空间变为From空间,这个空间要继续承担内存分配,如果占比过高会影响后续的内存使用;

下面说说老生代:

老生代的中存放的数据垃圾回收主要采用标记清除(Mark-Sweep)和标记整理(Mark-Compact)。这两种方式并非互相替代关系,而是配合关系,在不同情况下,选择不同方式,交替配合以提高回收效率。

当老生代的垃圾回收被触发的时候,v8会给还存活的对象打上标记,然后把没有标记的对象全部清除,这就是一次标记清除;

可是随着程序的继续运行,却会出现一个问题:被清除的对象遍布各个内存地址,空间有大有小,其闲置空间不连续,产生了很多内存碎片。当需要将一个足够大的对象晋升至老生代时,无法找到一个足够大的连续空间安置这个对象。
为了解决这种空间碎片的问题,就出现了标记整理算法。它是在标记清除的基础上演变而来,当清理了死亡对象后,它会将所有存活对象往一端移动,使其内存空间紧挨,另一端就成为了连续内存;
 
增量标记::----- 早期V8在垃圾回收阶段,采用全停顿,也就是垃圾回收时程序运行会暂停;这在前端使用js时还没有缺点显现,但是在node中,内存使用高,在老生代的垃圾回收中,标记时间很容易超过100ms,全停顿导致程序卡滞很明显,于是v8引入了增量标记,将标记动作分成若干个步骤,每运行一段时间标记动作,就停下运行一段时间程序,如此交替,程序运行流畅了很多

我觉得 https://www.jianshu.com/p/4129a3fce7bb 这篇文章写得很详细;

原文地址:https://www.cnblogs.com/javencs/p/11401279.html