垃圾回收机制
本文最后更新于:8 个月前
V8的垃圾回收机制,内存泄漏是什么?如何避免内存泄漏?
垃圾回收&内存泄漏
虽然垃圾回收是引擎自动完成的工作,但是我们依然需要了解它,意义就在于:了解之后,我们能够清楚如何主动规避一些不利于引擎做GC的操作,从而避免内存泄露
一、什么是GC
Garbage Collection,垃圾收集。程序运行过程会产生很多不用的内存、或者用过了不再使用的内存,我们称之为“垃圾”。GC垃圾回收,就是指回收这些没有用的内存空间。这个过程是由浏览器自动完成的。
高级语言里会自带GC,如:Java、Python、JavaScript。但是C、C++没有。
JS里面区分栈内存、堆内存,分别保存不同的数据类型,那么垃圾回收机制在这两者上是否有区别呢?
二、垃圾是怎么产生的
举个例子:
1 |
|
第一行代码在内存中创建了一个对象A,
第二行代码,当test执行另一个数组B时,对象A就失去了作用,它就成为了内存“垃圾”
三、V8的GC
1、标记清除算法
如何确定内存中那些内容是应该删除的垃圾?
概念:可达性
以某种方式可以访问到的值,就是具有可达性的值,它们应该保存在内存中。反之,不可达的值则需要回收,释放对应的内存空间。至于如何发现不可达的值,V8引擎采用的是标记清除算法。定期执行该算法来GC
标记阶段:
第一步,给内存中所有值标记一个0值
第二步,从一组「根」对象(如Window对象、DOM树)出发,遍历内存中所有对象,标记上1值
清除阶段:
第三步,销毁所有标记为0的值,回收对应的内存空间
优点:实现简单
缺点:回收后的内存空间不连续(下面就解决这个问题)
2、标记整理算法
据说标记阶段与「标记清除算法」的过程一样,但是不太懂为什么整理内存还要进行标记
该算法达到的效果是:将仍存在在内存中的对象向内存一侧移动,从而清出大片完整的空闲内存。
3、V8的GC优化
优化背景:
如果对内存中体积大、存活时间长的对象,与体积小、存活时间短、较新的内存对象采用同样的检查频率显然不够合理。
优化方式:
分代垃圾回收,区分新老生代,将堆内存分为两部分,分别保存新老两代的对象
新生代:清理频率高,内存较小,保存存活时间短、新产生的对象
老生代:清理频率低,内存较大,保存存活时间长、体积较大的对象
对于新老两块内存区域的垃圾回收,V8 采用了两个垃圾回收器来管控
4、新生代GC
回收算法:Scavenge算法 + 并行回收(多线程)
回收过程:将(新生代)堆内存一分为二:「使用区」、「空闲区」,将新对象都放入使用区、快写满的时候执行垃圾清理
第一步,标记使用区中的活动对象
第二步,将使用区的活动对象复制进空闲区并排序(为什么要排序?)
第三步,将使用区内所有对象销毁,并转换「使用区」和「空闲区」(即原来的空闲区变成现在的使用区)
5、老生代GC
回收算法:标记清除算法、标记整理算法 + 增量标记(三色标记法+写屏障)
回收过程:
第一步,将(老生代)堆内存中所有对象标记为0
第二步,从一组根元素开始,遍历内存中的活动对象,标记为1
第三步,清除所有标记为0的对象
第四步,执行标记整理算法,清理内存碎片
「增量标记」带来的问题:
如果仍然使用0、1两种标记,将无法获悉下一个标记阶段的起点 ==》 「三色标记法」
「三色标记法」
黑色:标记过的活动对象
白色:标记过的非活动对象 、 还没有标记的对象
灰色:正在进行标记的对象(下个标记阶段的起点)
「三色标记法」存在的问题:增量中引用发生修改
为黑色对象添加一个新的引用对象,该对象默认为白色,将会被清除,造成访问不到的错误 ==》 写屏障
「写屏障」
一旦有黑色引用白色对象,白色对象强制变为灰色,确保下一次增量GC时能被正确标记
四、什么是「内存泄漏」
不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)
这里需要对「闭包」特别说明一下。虽然闭包也会占用着内存不释放,但这是属于我们预料之内的,所以应该不能将「闭包」归入「内存泄漏」的范畴。当然这也不是说可以随便使用、滥用闭包了。
五、如何发现「内存泄漏」
可以参考这篇文章:手把手教你排查Javascript内存泄漏 - 知乎 (zhihu.com)
六、导致「内存泄漏」的错误示例
1. 被遗忘的计时器
setTimeout
和setInterval
两个定时器如果没有clearTimeout
/clearInterval
注销掉,它内部的回调函数和依赖的变量都不能被GC回收,所以会导致「内存泄漏」。
解决方式:
- 不再需要定时器事件事,手动将其注销
2.不必要的全局变量
全局变量绑定在window对象上,在网页整个生命周期内都属于可达到活动对象,所以不会被GC销毁。
除了尽量避免使用全局变量之外,还需要注意不要出现意外的全局变量,如下:
1 |
|
1 |
|
解决方式:
- (1)使用严格模式,在js文件头部添加
use strict
,可以避免“意外的全局变量” - (2)将不在使用的全局变量赋值为
null
,可以释放掉对应的内存
3.频繁的console输出
被console
使用的对象是不能被垃圾回收的,导致内存泄漏
4.DOM泄漏
1 |
|