JavaScript垃圾回收机制
文章目录
垃圾回收策略
不同的语言有不同的垃圾回收策略
- 手动回收:C/C++
- 何时分配内存、何时销毁内存都时由代码控制的
- 自动回收:JavaScript、python、Java
- 产生的垃圾数据由垃圾回收器来释放
调用栈中的数据是如何回收的
有一个记录执行状态的指针(ESP),当某个函数(能够创建执行上下文的代码)执行完毕,ESP就会下移,这个下移操作就是销毁已经执行完函数的上下文过程
所以:调用栈中的数据是通过向下移动ESP来销毁函数保存在栈中的执行上下文
堆中的数据如何回收
虽然调用栈的数据被回收了,但是调用栈中变量引用了堆中的数据是没有被销毁的
这个时候,就需要用到JavaScript中的”垃圾回收器“了
代际假说和分代收集
代际假说
- 大部分对象在内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得不可访问
- 不死的对象,会活得很久,例如:window、document、window下的全局对象
通常,垃圾回收算法有很多种,但是并没有哪一种能胜任所有的场景,需要权衡各种场景,根据对象的生存周期的不同而使用不同的算法,以便达到更好的效果。
所以:V8把堆分为”新生代“和”老生代“两个区域,新生代种存放的是生存时间短的对象,老生代存放的是生存时间久的对象
新生区通常只支持1-8M的容量,而老生区支持的容量就大很多了。对于这两块区域,V8分别使用两个不同的垃圾回收器,以便更高效的实施垃圾回收
副/主垃圾回收器
- 副垃圾回收器,主要负责新生代的垃圾回收
- 主垃圾回收器,主要负责老生代的垃圾回收
垃圾谁后期的工作流程
无论什么类型的垃圾回收器,它们都有一套共同的执行流程
- 标记空间的”活动对象“和”非活动对象“
- 活动对象:还在使用的对象
- 非活动对象:可以进行垃圾回收的对象
- 回收”非活动对象“所占据的内存。其实就是在标记完成之后,统一清理内存种所有被标记为可回收的对象
- 内存整理(可选
- 频繁回收对象后,内存种就存在大量不连续空间,我们把这些不连续的内存空间称为”内存碎片“
- 当内存中出现了大量的内存碎片之后,如果需要分配较大连续内存的时候,就可能出现内存不足的情况。
- 所以需要整理这些内存碎片
副垃圾会回收器
主要负责:新生区的垃圾回收。而通常情况下,大多数小的对象都会被分配到新生区,所以说,这个区域虽然不大,但是垃圾回收还是比较频繁的。
Scanvenge算法
把新生代空间对半划分为两个区域,一般是对象区域,一般是空闲区域
新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。
在垃圾回收过程中,首先要对对象区域中的垃圾做标记;标记完成之后,就进入垃圾清理阶段,副垃圾会后器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,所以这个复制过程,相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了。
完成复制后,对象区域与空闲区域继续”角色翻转“,也就是原来地对象区域变成空闲区域,原来地空闲区域变成了对象区域。这样就完成了垃圾对象地会后操作,同时这样”角色翻转地操作还能让新生代中地两块区域无线重复使用下去“
缺点:
每次执行清理操作时,都需要将存活地对象从对象区域复制到空闲区域。但复制操作需要时间成本,如果新生代空间设置太大了,那么每次清理地时间就会过很久,所以”为了执行效率,一般新生区地空间会被设置得比较小“
也就是因为新生区地空间不大,所以很容易被存活地对象装满整个区域。为了解决这个问题,JavaScript引擎采用了”对象晋升策略“,也就是经过两次垃圾回收依然存活地对象,会被转移到老生去中。
主垃圾回收器
负责对象:
- 新生区晋升地对象:存活时间长
- 一些大的对象会被直接分配到老生区:占用空间大
标记-清除算法
-
标记过程阶段:从一组根元素开始,递归遍历这个根元素,在这个遍历过程中,能到达的元素称为”活动元素“,没有到达的元素可以判断为”垃圾数据“
-
垃圾清除过程:清除掉空色标记的数据
- 这样会长生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是又长生了另外一种算法——”标记-整理“
标记-整理
主题过程和”标记-清除“算法一样,但是后续步骤不是直接对可回收对象进行整理,而是让所有活动对象都向一端移动,然后清理掉端边界以外的内存
全停顿
已经知道V8是使用副垃圾回收器和主垃圾回收器处理垃圾回收的,不过由于JavaScript是运行在主线程之上的,一旦执行垃圾回收算法,都需要将正在执行的JavaScript脚本停顿下来,待垃圾回收完毕后再回复脚本执行。
在V8新生代的垃圾回收中,因其空间较小,且存活对象较少,所以全停顿的影响不大,但老生代就不一样了。
增量标记算法
为了降低老生代的垃圾回收而造成的卡顿,V8将标记过程分为一个个的子标记过程,同时让垃圾回收标记个JavaScript应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为”增量标记算法“
因为将一个完整的垃圾回收任务拆分为很多小的任务,这些小的任务执行时间比较短,可以穿插在其他JavaScript任务中间执行,这样当执行动画效果时,就不会让用户因为垃圾回收任务而感受到页面的卡顿了。