js的垃圾回收机制

Authors
  • avatar
    Name
    小明&小艺
    Twitter

简介

js 无法管理内存单元,内存的分配和回收都是自动进行的。但是自动并不意味着可以不用管。如果自动分配的内存,因为使用问题而无法释放,这就会造成内存泄露了。要了解 js 的垃圾回收,先得清楚内存的生命周期。不管什么语言,内存生命周期基本一致:分配所需空间 --> 读写分配的内存空间 --> 不需要的时候将其释放\归还 。那垃圾回收则是跟第三步相关。那回收的关键就落在如何判断“不需要”了。全局变量不会被回收,局部变量才可以回收。

要想完全将无用的内存释放掉,显然是无法通过某种算法做到的,垃圾回收只能近似地把无用的内存回收。而这种近似地算法有:引用计数和标记-清除。

引用

垃圾回收算法主要依赖于引用的概念。js 地数据类型分为基本类型和引用类型,引用类型就是为了解决复杂数据结构的数据不要存储多份的问题。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。例如,一个 Javascript 对象具有对它 原型 的引用(隐式引用)和对它属性的引用(显式引用)。

引用计数法

给每个分配的变量一个 count 记录引用的次数,当函数结束的时候,会扫描一下这个计算,如果变量的引用次数为零,则表明该变量没有引用指向它,则其可被回收。

var o = {
  a: {
    b:2
  }
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集
var o2 = o; // o2变量是第二个对“这个对象”的引用

o = 1;      // 现在,“这个对象”的原始引用o被o2替换了
var oa = o2.a; // 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa

o2 = "yo"; // 最初的对象现在已经是零引用了
           // 他可以被垃圾回收了
           // 然而它的属性a的对象还在被oa引用,所以还不能回收
oa = null; // a属性的那个对象现在也是零引用了
           // 它可以被垃圾回收了

这个算法的不足是:如果一个对象引用另一个(形成了循环引用),他们可能“不再需要”了,但是他们不会被回收。 所以才有了标记-清除算法。

标记-清除算法

在标记清除算法里改变了策略,不以引用为 0 作为标识,而是以是否可达,即是否可以有跟对象访问得到。即使存在循环引用,但是由根不可达的时候,也将其回收。这个算法中有两个角色:

  • collector:垃圾收集器
  • mutator:内存使用者,可以分配、读取、写入内存

collector 可以回收不再使用的内存来供 mutator 进行 NEW 操作的使用。

其有两个阶段:标记(mark)和清除(sweep),

  • 标记阶段:collector 从 mutator 根对象开始进行遍历,对从 mutator 根对象可以访问到的对象都打上一个标识,一般是在对象的 header 中,将其记录为可达对象。

  • 清除阶段:collector 对堆内存(heap memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象-通过读取对象的 header 信息,则就将其回收。

垃圾回收机制的时机

垃圾回收机制是由程序自动执行的,其会根据需要来执行回收程序。delete,并不会触发垃圾回收,与直接释放内存(只能通过解除引用来间接释放)没有关系。

(Node 与其他语言不同的一个地方,就是其限制了 JavaScript 所能使用的内存(64 位为 1.4GB,32 位为 0.7GB))