一. 前言
当我们创建对象时,js会在内存空间开辟地址来存放这个对象的内容。随着软件的运行,创建的对象会越来越多,而当新创建的对象不断添加,不再被使用的老对象却没有被及时清理时,内存占用就会越来越多,造成程序的运行缓慢直到崩溃。这个过程就称为内存泄漏(Memory Leak)。
二. 垃圾回收算法
垃圾回收(Garbage Collection, 简称GC) 便是起到清理内存中不再用到的资源的手段。在一些编程语言如C++中,程序员需要时刻关注内存使用并在适当的时候手动清理。而在JS中,浏览器会启用一个垃圾回收器,每过一段时间便会寻找不被使用的资源并清理,这个过程是隐藏且自动的。目前前端使用的垃圾回收机制主要有两种:引用计数法和标记清除法。
2.1 引用计数法(Reference counting)
2.1.1 原理 :
当新变量指向对象时,该对象引用计数+1。当某一变量不再指向该对象时,引用计数-1。当引用计数为0,清理该对象。
function () {
const a = new Object();
// Object被创建,被变量a指向,引用计数+1 = 1
const b = a;
// 新变量b指向了Object,引用次数+1 = 2
}
// 离开块级作用域,临时变量a,b被清除,Object的引用次数-2 = 0,Object被清除
以图1为例,在function中创建对象以及指向该对象的两个变量,该对象同时被两个变量所引用,因此引用计数为2。图2展示了退出function块级作用域的状态,此时a,b变量都被清除,对于Object的引用自然也被清除,Object没有被任何东西引用,因此接下来会被垃圾回收。
2.1.2 问题:
引用计数法无法处理循环引用问题。
function () {
const a = new ObjectA();
const b = new ObjectB();
// ObjectA, ObjectB被创建,引用计数都为1
a.object = b;
b.object = a;
// ObjectA, ObjectB互相引用,引用次数各+1都为2
}
// 离开块级作用域,临时变量a,b被清除,ObjectA, ObjectB的引用次数各-1 = 1
图3展示了function块内状态,ObjectA,ObjectB除去变量a,b的引用外还有彼此间的一个引用,此时引用计数都为2。而进入图4的状态后,来自临时变量a,b的引用被清除,Object彼此间的引用仍保留。从外部来看,没有任何方法能够访问到这俩Object,但是由于他们的引用计数不为0,因此无法回收掉这块无效占用的内存。
2.2 标记清除法(mark-sweep)
标记清除法的关键字是可达性。在引用计数法最后一个例子中,互相引用的ObjectA和ObjectB无法被外部变量访问,处于不可达状态。与引用计数法通过计算引用次数不同,标记清除法会使得浏览器清空所有不可达的资源。标记清楚法能够很好的解决引用计数法造成的循环引用的问题,因此现阶段大部分主流浏览器都是使用的引用计数法。
2.2.1 原理:
标记清除分为三个阶段:
- 给所有进入环境的资源打上标记
- 从根(一般指全局变量)开始通过引用链访问所有可达的对象并清除标记
- 删除所有仍拥有标记的资源。
三. 链接