- JavaScript 不像 C/C++, 让程序员自己去调用函数来释放内存,而是拥有自己的一套垃圾回收算法进行自动的内存管理。
垃圾回收有两种方法:标记清除、引用计数, 引用计数不太常用,标记清除较为常用。
#引用计数算法
第一个就是早先的引用计数法,它的策略是跟踪记录每个变量值被使用的次数,当这个值的引用次数变为 0 的时候,说明没有其他对象引用他了,垃圾回收器会在运行的时候清理掉引用次数为 0 的垃圾对象。
#缺点
无法解决循环引用的两个对象无法回收的问题,因为循环引用的两个对象的引用次数都不为 0。
#优点
引用计数在引用值为 0 时,可以立即回收垃圾
#标记清除算法
第二个就是目前大多数浏览器的 JavaScript 引擎 都在采用标记清除算法,就像它的名字一样,此算法分为 标记 和 清除 两个阶段。
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设标记策略时全标记为 0。
- 然后从各个根对象开始遍历,把可达的对象也就是不是垃圾的节点改成 1。
- 清理所有标记为 0 的垃圾对象,销毁并回收它们所占用的内存空间
- 最后,把所有内存中对象标记修改为 0,等待下一轮垃圾回收。
#缺点
- 内存碎片化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块。
#优化垃圾清除算法的原因
- JS 是单线程运行的,这意味着一旦进入到垃圾回收,那么其它的运行的业务逻辑都要暂停; 另一方面垃圾回收其实是非常耗时间的操作。
#标记清除算法优化
#优化 1:标记整理(Mark-Compact)算法
- 垃圾清理之后,会存在大量不连续的内存碎片,所以在标记结束后,标记整理算法会将不需要清理的对象对向内存的一端移动,最后清理掉边界的内存。从而使得空闲内存块是连续的。
#优化 2:分代式垃圾回收
- V8 的垃圾回收策略主要基于分代式垃圾回收机制,V8 中将堆内存分为新生代和老生代两区域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收 新生代的对象为存活时间较短的对象,简单来说就是新产生的对象,通常只支持 1 ~ 8M 的容量,而老生代的对象为存活事件较长或常驻内存的对象,简单来说就是经历过新生代垃圾回收后还存活下来的对象,容量通常比较大。
#新生代垃圾回收
- 新生代垃圾回收所依赖的算法是
Scavenge算法。 - 首先将新生代内存空间一分为二:分为使用区和空闲区,当进行垃圾回收时,V8 将 使用区 部分的对象检查一遍,如果是存活对象那么按照内存顺序排列并复制到 空闲区 内存中,如果是非存活对象直接回收即可。当所有的 空闲区 中的存活对象按照顺序进入到 使用区 内存之后,空闲区 和 使用区 两者的角色对调,空闲区 现在被闲置,使用区 为正在使用,如此循环。
#老生代垃圾回收
- 老生代垃圾回收所依赖的算法是
标记清除算法。 - 新生代中的变量如果经过回收两次之后依然存在,采用对象晋升策略,放入到老生代内存中。
- 老生代当中就是先使用标记清除算法来进行垃圾回收,然后再通过标记整理来解决内存碎片化问题。
#优化 3:增量标记
- 将标记任务分为很多小的部分完成,每做完一个小的部分 就让 JavaScript 应用逻辑执行一会儿,然后再做下面的部分,如此循环,直到标记阶段完成。