1 引言
垃圾回收机制(Garbage Collection)简称 GC。js中的内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
2 内存的生命周期
js环境中分配的内存,一般有如下的生命周期:
1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为它们分配内存;
2. 内存使用:即读写内存,也就是使用变量、函数等;
3. 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存;
说明:
1. 全局变量一般不会回收(关闭页面才会被回收);
2. 一般情况下局部变量的值,不被使用了,会自动被回收;
3. 程序中分配的内存 由于某种原因,程序未释放或无法释放叫做内存泄漏;(1.下面会提到两个对象相互引用会造成内存泄漏;2. 闭包的一个缺点就是会造成内存泄漏)
3 垃圾回收机制的算法说明
3.1 堆栈空间分配的区别
1. 栈(操作系统):由于操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面;
2. 堆(操作系统):一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面;
3.2 常见浏览器的垃圾回收算法
3.2.1 引用计数算法
IE 浏览器采用的是引用计数算法,定义 “内存不再使用”, 就是看一个对象是否有指向它的引用,没有被引用了,就回收。
3.2.1.1 算法思路
1. 跟踪记录被引用的次数;
2. 如果被引用了一次,那么记录次数就为1, 多次被引用, 则++;
3. 如果减少一个引用就减一, -- ;
4. 如果引用次数是0, 则释放内存;
3.2.1.2 绘图说明
代码示例1:
const arr = [1, 2, 3]
arr = null
数组是复杂数据类型,所以在栈中存的是地址,arr 通过栈中的地址找到堆中的 [1, 2, 3]
<1>. 当执行第一行代码时:
<2>. 当执行第二行代码时:
此时arr被赋值null, null数简单数据类型, 存在栈中
<3>. 被引用次数为0,就会自动被回收了
代码示例2;
let person = {
name: '张三',
age: 18
}
let person2 = person
person = 1
person = null
<1>. 代码从上往下执行,对象是复杂数据类型,栈中存地址,堆中存的是数据:
<2>. 当代码执行到 let person2 = person 时
将person赋值给person2, 但是复杂数据类型赋的是地址, 所以此时 perosn2 与 person指向相同的地址, 所以person 和 person2指向堆中相同的数据,引用次数变为了 2
<3>.当执行 person = 1 时
此时person被1赋值, 1属于简单数据类型,存在栈中, 地址改变了,找不到堆中的数据了,所以引用次数减 1
<4>.当执行person2 = null 时
跟上面一样,person2被赋值了简单的数据类型,不再指向堆中的数据,被引用次数再次减去1, 变为了0 ,此时堆中的数据没有被引用了, 会被系统自动回收
3.2.1.3 引用计数法存在的问题
嵌套(循环)引用问题
代码示例:
function fn(){
let obj1 = {}
let obj2 = {}
obj1.a = obj2
obj2.a = obj1
return '引用次数无法回收!'
}
fn()
<1>.当执行 let obj1 = {}
let obj2 = {} 这两行代码时,按道理,函数作用域内属于局部作用域,使用完,会被系统回收,
<2>. 但是出现了这两行代码, 问题就出现了, obj1.a = obj2
obj2.a = obj1
所以,如果两个对象相互引用,尽管它们已不再被使用,垃圾回收器也不会进行回收,就会导致内存泄漏。
3.2.2 标记清除法
算法思想:
1. 标记清除法将 “不再引用的对象” 定义为 “无法到达的对象”;
2. 就是从根部(在JS中就是从全局对象)出发定时扫描内存中的对象,反是能从根部到达的对象,都是还需要使用的;
3. 那些无法从根部出发而触及到的对象被标记为不再使用的,稍后进行回收;
所以,标记清除法 解决了引用计数法的嵌套引用的问题