参考文献:
1.http://www.manongjc.com/detail/5-fwwvmtvwvmgzkiz.html
2.https://www.cnblogs.com/gg-qq/p/13862251.html
3.https://zhuanlan.zhihu.com/p/348988741
垃圾回收
我们在写js代码的时候,会频繁的操作数据。一些数据不需要的时候,就是垃圾数据,垃圾数据占用的内存就会被回收。
找到不再被使用的变量,然后释放其占用的内存,但这个过程不是时时的,因为其开销比较大,
所以垃圾回收器会按照固定时间间隔周期性的执行
变量的生命周期
比如:
let dog = new Object();
let dog.a = new Array(1);
- 当js执行这段代码,会在全局添加dog属性,并且在堆中创建了一个空对象,将该对象的地址指向dog.
- 随后又创建一个数组,并把属性的地址指向dog.a,如下图:
如果此时,我将另外一个对象赋给了 a 属性,代码如下所示:
dog.a = new Object()
此时的内存布局图:
a 的指向改变了, 此时堆中的数组对象就成为了不被使用的数据,专业名词叫不可达的数据。
这就是需要回收的垃圾数据。
垃圾回收方式
标记清楚
当变量进入环境,被标记为进入,当变量离开环境,则标记为“离开”。标记离开的就回收内存。
引用计数(低版本浏览器)
变量声明,第一次赋值为1,然后当这个变量值改变,记录为0,将技术为0的收回。
内存泄漏
1.意外的全局变量引起的内存泄露
原因: 全局变量不会被回收
解决:使用严格模式避免
2.闭包引起的
原因: 活动对象被引用,使闭包内的变量不会被释放
解决: 将活动对象赋值为null
3.被清理的DOM元素的引用
原因: 虽然DOM被删掉了,但对象中还存在对DOM的引用
解决: 将对象赋值为null
4.被遗忘的定时器或回调
原因: 定时器内部实现闭包,回调也是闭包
解决: 清理定时器clearInterval、null
垃圾回收算法
1.标记空间可达值
v8采用的是可达性(reachability)算法来判断堆中的对象是不是应该被回收。
算法思路:
-
从根节点除法,遍历所有对象。
-
可以遍历到的对象,是可达的。
-
没有遍历到的对象,是不可达的。
浏览器环境下有很多根节点,如下: -
全局变量window,位于每个ifream中
-
DOM树
-
存放在栈上的遍历
2. 回收【不可达】的内存
所有标记完成后,统一清理不可达的对象。
3. 做内存整理
频繁回收对象后,内存回出现大量不连续的空间,叫做内存碎片。
当分配较大连续内存的时候,可能会出现空间不足,所以需要整理内存碎片。(可选,副垃圾回收器不会产生内存碎片)
4.分代收集(世代假说)
垃圾回收的策略是建立在代际假说之上,代际假说的2个特点如下:
- 大部分对象在内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得不可访问;
- 不死的对象会活得更久
基于代际假说,V8 分别使用两个不同的垃圾回收器,以便更高效地实施垃圾回收
浏览器将数据分为两种,1.临时对象,2.长久对象。
临时对象:
- 大部分对象在内存存活很短。
- 函数内声明的遍历,或者会计作用域中的变量。当函数或者代码块执行结束,作用域中定义的变量就会被销毁。
- 这类对象不可访问,可以马上回收。
长久对象:
生命周期长的对象,比如全局window,DOM,Web API等等。可以慢点回收。
针对于这两种有不同的回收策略,**V8把堆分成新生代(临时对象)和老生代(长久对象)**两个区域。
并且让副垃圾回收器负责新生代的垃圾回收,主垃圾回收器负责老生代的垃圾回收。
新生代 - 副垃圾回收器
新生区通常只支持 1~8M 的容量,使用Scavenge 算法来处理。
新生区主要存放内存占用小、对象存活时间短的对象。扫描频率更高一些。
新生区将内存划分为2部分,将对象都放入左侧对象区域,扫描时:
- 依次对对象区域中的垃圾做标记
- 将存活的对象复制到空闲区域中
- 清空对象区域
- 将原来的空闲区域变成了对象区域
老生代 - 主垃圾回收器
老生区占用大多数内存,同时也存放那些占用空间大、对象存活时间长的对象。
因为老生区通常都是较大的对象,采用新生代那样的赋值方式会花费大量的时间,导致执行效率低,同时还会浪费一半的内存空间。因而,主垃圾回收器是采用标记 - 清除(Mark-Sweep)的算法进行垃圾回收的:
- 从根元素递归变量所有对象,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据
- 清除垃圾数据,整理磁盘内存
单线程的JavaScript在垃圾回收阶段将会阻塞线程,因此老生代的刷新频率低于新生代。
除此之外,V8还使用了增量标记(Incremental Marking)算法,将清理全过程拆解为一个一个的小任务,避免造成页面卡顿、阻塞的情况。
对象晋升策略
一个对象,是被『分』给新生代,还是老生代?划分依据一个是占用内存大小,一个是存活时间。
一个比较小的对象,初始化被分为到新生代。如果经过两次垃圾回收依然还存活,我们就认定它是活跃对象,将其从新生代移动到老生代中。
磁盘整理
新生代与老生代在清理数据时都面临产生磁盘碎片的问题。
新生代会在移动过程中『顺手』存放在连续的内存中。而老生代就没那么容易了。
老生代清理掉红色的垃圾数据后,会产生大量不连续的内存碎片,而碎片过多会导致大对象无法分配到足够的连续内存,于是又产生了另外一种算法——标记 - 整理(Mark-Compact)
后续的清理,不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
浏览器中不同类型变量的内存都是何时释放?
Javascritp 中类型:值类型,引用类型。
-
引用类型:
在没有引用之后,通过 V8 自动回收。 -
值类型:
如果处于闭包的情况下,要等闭包没有引用才会被 V8 回收。
非闭包的情况下,等待 V8 的新生代切换的时候回收。