垃圾回收:js是使用垃圾回收的语言,通过自动内存管理实现内存分配和闲置资源回收。
基本思路: 确定哪个变量不再使用,然后释放它占用的内存。这个过程是周期性的,每隔一段时间就自动运行,开发者不用关心内存分配和回收。
- 离开作用域后的值会被自动标记为可回收,然后在垃圾回收期间被删除
- 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它的内存
- 引用计数是另一种方式,需要记录值被引用的多少次。
什么是垃圾数据
我们在写js代码时,会频繁操作数据。在一些数据不被需要的时候,它就是垃圾数据。这些垃圾数据占用的内存应该被回收。
const dog = new Object()
dog.a = new Array()
当js执行代码时,会首先在全局作用域中添加一个dog属性,并在堆中创建一个空对象,将该对象的地址指向dog。随后又创建了一个大小为1的数组,并将属性地址指向了dog.a
如果我们将dog.a = new Object()
a指向改变了,此时堆中的数组对象就成了垃圾数据,专业名词 【不可达】数据,需要被回收。
垃圾回收算法
可以将过程想象成一个从根溢出的巨大的油漆桶,它从一个根节点出发,将可到达的对象标记染色,移除未标记的。
标记空间中【可达值】的值
V8采用的是可达性算法来判断堆中对象应不应该回收。思路:
- 从根节点出发, 遍历所有的对象
- 可以遍历到的对象,是可达的
- 没有被遍历的,是不可达的
回收【不可达】的值所占的内存
标记完后,统一清理内存中所有不可达的对象
内存整理
- 频繁回收对象后,内存中就会出现大量不连续空间,专业名词【内存碎片】
- 当内存中出现大量内存碎片,如果需要分配较大连续内存时,就有可能出现内存不足的情况。
- 最后一步是整理内存碎片
什么时候垃圾回收
浏览器进行垃圾回收时,会暂停了js脚本,等回收完成再继续进行对于普通应用没什么问题,但对于js游戏,动画对连贯性要求较高的应用时,造成页面卡顿。那什么时候回收垃圾,可以避免长时间暂停。
- 分代收集
- 增量收集
- 闲时收集
分代收集
浏览器将数据分为两种:
临时对象
- 大部分对象在内存中存活时间短。
- 函数内部声明的变量,函数执行完,变量被销毁。
- 变量不可访问,应快回收。
长久对象
- 生命周期长的对象,全局window,DOM,WEBAPI等。
- 这类对象可以慢点回收。
针对这两种对象,V8将堆分为:
- 新生代区域(存放临时对象)==》 副垃圾回收器回收
- 老生代区域(存放持久对象)==》 主垃圾回收器回收
主垃圾回收器
负责老生代区域中的长久对象, 特点:
- 占用空间大
- 对象存活时间长
它使用【标记 - 清除】的算法执行垃圾回收
- 从一组根元素开始,递归遍历这组根元素,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
- 垃圾清除
- 内存整理(多次清除后,会产生大量的不连续的内存碎片,需内存整理)
副垃圾回收器
负责新生代区域中的临时对象的回收,通常只有1-8M的容量。新生代被分为两个区域:
- 新生区
- 空闲区
新加入的对象都被放进对象区域,等对象区域满后,会执行一次垃圾清理。
步骤:
- 先给对象区域所有垃圾做标记
- 标记完后,将存活的对象复制到空闲区域中,并有序排列
副垃圾回收器是没有碎片整理的,因为空闲区本来就是整齐的。
- 空闲区与对象区对调
总结: 副垃圾回收操作比较频繁,为了执行效率,一般新生取空间设置的较小,一旦满了就进行垃圾回收。
分代收集:将堆分为新生代和老生代,多回收新生代,少回收老生代。这样就减少了每次需要遍历的对象,从而减少垃圾回收耗时。
增量收集
如果脚本中含有许多对象,引擎一次性遍历整个对象,会造成长时间暂停。所以引擎将垃圾收集工作分成更小的块,每次处理一部分,多次处理。 这样就解决了长时间停顿的问题。
闲时收集
垃圾回收器只会在CPU空闲时尝试运行,以减少可能对代码执行的影响。
面试题
1. 浏览器怎么垃圾回收?
三点回答: 什么是垃圾, 如何捡垃圾, 什么时候捡垃圾
什么是垃圾: 不再需要的数据即为垃圾,全局变量随时可以用到,不是垃圾
如何捡垃圾:(遍历算法)
- 标记空间中可达值
- 回收不可达值占据的内存
- 做内存整理
什么时候捡垃圾
- 前端其特殊性,垃圾回收可能会造成页面卡顿
- 分代收集、增量收集、闲时收集