JS基础巩固系列:【3】js垃圾回收机制

垃圾回收机制的必要性

我们引用《JavaScript权威指南(第四版)》的一段话来解释一下:由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。

js是如何实现垃圾回收的

js有自己的一套垃圾回收机制(Garbage Collection)。JavaScript的解析器可以监测到何时程序不再使用一个对象了,当确认这个对象是无用的时候,就可以把他的内存释放掉了,上例子:

1
2
3
var a = 'before';
var b = 'override a';
a = b;

这段代码运行后,’before’已经失去了引用,系统检测到后,就会释放’before’所占的存储空间。

垃圾回收原理

现在各大浏览器通常使用的垃圾回收有两种方式:标记清除和引用计数

  1. 标记清除
    这是 JavaScript 中最常见的垃圾回收方式。为什么说这是种最常见的方法,因为从 2012 年起,所有现代浏览器都使用了标记-清除的垃圾回收方法,除了低版本 IE…它们采用的是引用计数方法。
    那什么叫标记清除呢?JavaScript 中有个全局对象,浏览器中是 window。定期的,垃圾回收期将从这个全局对象开始,找所有从这个全局对象开始引用的对象,再找这些对象引用的对象…对这些活着的对象进行标记,这是标记阶段。清除阶段就是清除那些没有被标记的对象。
    工作流程:
  • 垃圾收集器会在运行的时候会给存储在内存中的 所有变量都加上标记 。
  • 去掉环境中的变量以及被环境中的变量引用的变量的标记。
  • 那些还存在标记的变量被视为准备删除的变量。
  • 最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间。
    标记-清除法的一个问题就是不那么有效率,因为在标记-清除阶段,整个程序将会等待,所以如果程序出现卡顿的情况,那有可能是收集垃圾的过程。
    标记-清除还有一个问题,就是在清除之后,内存空间是不连续的,即出现了内存碎片。如果后面需要一个比较大的连续的内存空间时,那将不能满足要求。而标记-整理方法可以有效地解决这个问题。标记阶段没有什么不同,只是标记结束后,标记-整理方法会将活着的对象向内存的一边移动,最后清理掉边界的内存。不过可以想象,这种做法的效率没有标记-清除高。
  1. 引用计数
    在内存管理环境中,对象 A 如果有访问对象 B 的权限,叫做对象 A 引用对象 B。引用计数的策略是将“对象是否不再需要”简化成“对象有没有其他对象引用到它”,如果没有对象引用这个对象,那么这个对象将会被回收。上例子:
    1
    2
    3
    4
    let obj1 = { a: 1 }; // 一个对象(称之为 A)被创建,赋值给 obj1,A 的引用个数为 1 
    let obj2 = obj1; // A 的引用个数变为 2
    obj1 = 0; // A 的引用个数变为 1
    obj2 = 0; // A 的引用个数变为 0,此时对象 A 就可以被垃圾回收了

但是引用计数有个最大的问题: 循环引用。

1
2
3
4
5
6
7
function func() {
let obj1 = {};
let obj2 = {};

obj1.a = obj2; // obj1 引用 obj2
obj2.a = obj1; // obj2 引用 obj1
}

当函数 func 执行结束后,返回值为 undefined,所以整个函数以及内部的变量都应该被回收,但根据引用计数方法,obj1 和 obj2 的引用次数都不为 0,所以他们不会被回收。
这种情况我们可以通过,给将变量赋值为null,来释放其引用。这个方法被称为“解除引用”