JavaScript - 垃圾回收
垃圾回收
什么是垃圾回收
在创建一些变量,函数,对象时,都需要分配内存,当这些值不再被使用的时候,js 就需要在合适的时候将这部分的内存进行回收,这就是垃圾回收机制
,对于一些大型应用来说,垃圾回收可以有效提高性能。在js里,执行垃圾回收是自动执行的,不对外提供任何接口。
垃圾回收的条件
联系下实际生活:我们会把什么样的东西送去回收呢,当然是 确定以后根本用不到的东西
在js里也是一样,对于再也无法访问到的值,就要进行回收
1 | // 第一步:创建一个对象并把内存地址赋值给user |
创建引用类型值的时候,赋值给变量的实质上是内存地址。执行上述代码的第一步之后,可以通过 window.user
访问到 {name: 'Leo'}
对象,
但是在执行 user = null
之后,这个变量对象已经 无法访问到 了,也就达到了可以被回收的条件
1 | var user = { |
互相引用
1 | // '盆友圈'函数 让传入的两个人成为盆友,并返回这个圈子 |
window
访问 circle
对象,circle
通过user1 user2
属性访问到 {name:'Leo'} {name:'John'}
两个对象,此时这三个对象都是可访问的。
如果接下来执行
1 | circle.use1 = null |
接着执行
1 | circle.user2.friend = null |
如果不执行上面的语句,而是直接执行
1 | cricle = null |
此时cricle
指向的对象user1 user2
,都将不能访问,即使他们之间内部是有互相引用关系
满足可被回收的条件:
-从当前执行环境以及作用域链上可访问的对象出发,只要不能直接或者间接被访问到的
这里需要考虑闭包的执行环境和作用域链
1 | var b = 2; |
从当前执行环境以及作用域链上可访问的对象出发,任何可以被访问的对象,都算是可访问对象
垃圾回收的算法
标记清除法
- 标记:从根节点(全局对象、当前执行环境、作用域上可访问的对象)出发,深度优先遍历所有可以访问的节点,打上’可访问’的标记
- 清除:从所有节点里,清除没有打标记的节点
引用计数法
先记下某个变量被引用的总次数,当被引用次数为0 时,就表示可以回收
这两种方法的区别:在互相引用的例子中,两个 user
都不能访问,但由于互相引用,被引用次数不为0,按照引用计数法 这两个对象就不会被清除,这是有问题的,所以从2012年开始所有的现代浏览器都是用 标记清除法
分代回收机制
堆结构的划分
- 新空间:大多数对象都分配在这里。很小
- 旧指针空间:包含大多数对象 指向 其他对象的指针
- 旧数据空间:仅包含原始数据的对象。在新空间存活一段时间后,会移动到此处。合起来称为旧空间
- 大对象空间:包含的对象大于其他空间的大小限制,大对象永远不会被垃圾收集器移动
- 代码空间:唯一具有可执行内存的空间
整体配合机制
- 在新空间分配新对象,直到空间充满,就触发 小型回收机制
- 在 小型回收机制中存活下2次的对象,就会被移动到 旧空间(根据数据特点分配到旧指针空间或旧数据空间)
- 旧数据空间内存达到一定值,触发大型回收机制
小型回收机制(周期发生在新空间,频率高,时间短速度快)
- 新空间分为:from空间 和 to空间(两个空间不会同时使用),to空间为实际的内存分配空间 from为临时容器
- 交换from空间和to空间 执行完后 to 变成空的 from是满的
- 从root开始访问from空间所有可访问对象,移入to空间或 旧空间(存活2次飞升)
- 清空from空间(筛完都是可回收的)
大型回收机制
- 开始所有对象为白色
- 从root对象出发 所有可以访问的对象用灰色标记 放入待处理队列
- 从待处理队列中取出灰色对象 自己标记黑色 ,它的引用对象标记灰色 放入待处理队列
- 重复3 直到待处理队列为空 所有可访问对象都变成黑色, 此时白色都可回收