垃圾回收

什么是垃圾回收

在创建一些变量,函数,对象时,都需要分配内存,当这些值不再被使用的时候,js 就需要在合适的时候将这部分的内存进行回收,这就是垃圾回收机制
,对于一些大型应用来说,垃圾回收可以有效提高性能。在js里,执行垃圾回收是自动执行的,不对外提供任何接口。

垃圾回收的条件

联系下实际生活:我们会把什么样的东西送去回收呢,当然是 确定以后根本用不到的东西

在js里也是一样,对于再也无法访问到的值,就要进行回收

1
2
3
4
5
// 第一步:创建一个对象并把内存地址赋值给user
var user = {
name: 'Leo'
}
user = null // 第二步:修改user的内存地址

创建引用类型值的时候,赋值给变量的实质上是内存地址。执行上述代码的第一步之后,可以通过 window.user 访问到 {name: 'Leo'}对象,
但是在执行 user = null 之后,这个变量对象已经 无法访问到 了,也就达到了可以被回收的条件

1
2
3
4
5
6
7
8
9
var user = {
name: 'Leo',
friend: {
name: 'John'
}
}
user.friend = null
user = null
// 比之前多增加了一个对象 `{name:'John'}`

互相引用

1
2
3
4
5
6
7
8
9
10
11
// '盆友圈'函数 让传入的两个人成为盆友,并返回这个圈子
function circleOfFriends(user1, user2) {
user1.friend = user2
user2.friend = user1
return {
user1,
user2
}
}

var circle = circleOfFriends({name: 'Leo'}, {name: 'John'})

window 访问 circle对象,circle 通过user1 user2属性访问到 {name:'Leo'} {name:'John'} 两个对象,此时这三个对象都是可访问的。

如果接下来执行

1
2
circle.use1 = null
// 此时要访问 {name:'Leo'} 对象,还可以通过 circle.user2.friend 来实现,它依然是可以被访问到的

接着执行

1
2
circle.user2.friend = null
// 此时 {name:'Leo'} 被彻底孤立,再也不能访问,它满足被回收的条件

如果不执行上面的语句,而是直接执行

1
cricle = null

此时cricle指向的对象user1 user2,都将不能访问,即使他们之间内部是有互相引用关系

满足可被回收的条件:

-从当前执行环境以及作用域链上可访问的对象出发,只要不能直接或者间接被访问到的

这里需要考虑闭包的执行环境和作用域链

1
2
3
4
5
6
7
8
9
var b = 2;

function getA() {
var a = 1;
return a
}

getA()
// 运行getA()之前,变量a是不能访问的,执行getA函数内部时,变量a就是可以访问的了

从当前执行环境以及作用域链上可访问的对象出发,任何可以被访问的对象,都算是可访问对象

垃圾回收的算法

标记清除法

  • 标记:从根节点(全局对象、当前执行环境、作用域上可访问的对象)出发,深度优先遍历所有可以访问的节点,打上’可访问’的标记
  • 清除:从所有节点里,清除没有打标记的节点

引用计数法

先记下某个变量被引用的总次数,当被引用次数为0 时,就表示可以回收

这两种方法的区别:在互相引用的例子中,两个 user都不能访问,但由于互相引用,被引用次数不为0,按照引用计数法 这两个对象就不会被清除,这是有问题的,所以从2012年开始所有的现代浏览器都是用 标记清除法

分代回收机制

堆结构的划分

  • 新空间:大多数对象都分配在这里。很小
  • 旧指针空间:包含大多数对象 指向 其他对象的指针
  • 旧数据空间:仅包含原始数据的对象。在新空间存活一段时间后,会移动到此处。合起来称为旧空间
  • 大对象空间:包含的对象大于其他空间的大小限制,大对象永远不会被垃圾收集器移动
  • 代码空间:唯一具有可执行内存的空间

整体配合机制

  • 在新空间分配新对象,直到空间充满,就触发 小型回收机制
  • 小型回收机制中存活下2次的对象,就会被移动到 旧空间(根据数据特点分配到旧指针空间或旧数据空间)
  • 旧数据空间内存达到一定值,触发大型回收机制

小型回收机制(周期发生在新空间,频率高,时间短速度快)

  • 新空间分为:from空间to空间(两个空间不会同时使用),to空间为实际的内存分配空间 from为临时容器
  • 交换from空间to空间 执行完后 to 变成空的 from是满的
  • 从root开始访问from空间所有可访问对象,移入to空间或 旧空间(存活2次飞升)
  • 清空from空间(筛完都是可回收的)

大型回收机制

  • 开始所有对象为白色
  • 从root对象出发 所有可以访问的对象用灰色标记 放入待处理队列
  • 从待处理队列中取出灰色对象 自己标记黑色 ,它的引用对象标记灰色 放入待处理队列
  • 重复3 直到待处理队列为空 所有可访问对象都变成黑色, 此时白色都可回收