JavaScript中的数据中原始数据类型是存储在 栈空间中的,引⽤类型的数据是存储在堆空间中的
不过有些数据被使⽤之后,可能就不再需要了,我们把这种数据称为垃圾数据。如果这些垃圾数据⼀直保存 在内存中,那么内存会越⽤越多,所以我们需要对这些垃圾数据进⾏回收,以释放有限的内存空间。
通常情况下,垃圾数据回收分为⼿动回收和⾃动回收两种策略。
调⽤栈中的数据是如何回收的
function foo(){
var a = 1
var b = {name:"极客邦"}
function showName(){
var c = "极客时间"
var d = {name:"极客时间"}
}
showName()
}
foo()

当foo函数执⾏结束之 后,foo函数的执⾏上下⽂会从堆中被销毁掉,那么它是怎么被销毁的呢?
如果执⾏到showName函数时,那么JavaScript引擎会创建 showName函数的执⾏上下⽂,并将showName函数的执⾏上下⽂压⼊到调⽤栈中
执⾏到 showName函数时,其调⽤栈就如上图所⽰。与此同时,还有⼀个记录当前执⾏状态的指针(称为 ESP),指向调⽤栈中showName函数的执⾏上下⽂,表⽰当前正在执⾏showName函数。
接着,当showName函数执⾏完成之后,函数执⾏流程就进⼊了foo函数,那这时就需要销毁showName函 数的执⾏上下⽂了。ESP这时候就帮上忙了,JavaScript会将ESP下移到foo函数的执⾏上下⽂,这个下移操 作就是销毁showName函数执⾏上下⽂的过程。

当showName函数执⾏结束之后,ESP向下移动到foo函数的执⾏上下⽂中,上⾯ showName的执⾏上下⽂虽然保存在栈内存中,但是已经是⽆效内存了。
当foo函数再次调⽤另外⼀个 函数时,这块内容会被直接覆盖掉,⽤来存放另外⼀个函数的执⾏上下⽂。
堆中的数据是如何回收的
当上⾯那段代码的foo函数执⾏结束之后,ESP应该是指向全 局执⾏上下⽂的,那这样的话,showName函数和foo函数的执⾏上下⽂就处于⽆效状态了,不过保存在堆 中的两个对象依然占⽤着空间

要回收堆中的垃圾数据,就需要⽤到JavaScript中 的垃圾回收器了。
垃圾回收器的⼯作流程
第⼀步是标记空间中活动对象和⾮活动对象。所谓活动对象就是还在使⽤的对象,⾮活动对象就是可以进⾏ 垃圾回收的对象。
第⼆步是回收⾮活动对象所占据的内存。其实就是在所有的标记完成之后,统⼀清理内存中所有被标记为可 回收的对象。
第三步是做内存整理。频繁回收对象后,内存中就会存在⼤量不连续空间,我们把这些不连续的 内存空间称为内存碎⽚。当内存中出现了⼤量的内存碎⽚之后,如果需要分配较⼤连续内存的时候,就有可 能出现内存不⾜的情况。所以最后⼀步需要整理这些内存碎⽚。但这步其实是可选的,因为有的垃圾回收器 不会产⽣内存碎⽚,⽐如副垃圾回收器。
副垃圾回收器
副垃圾回收器主要负责新⽣区的垃圾回收。
在垃圾回收过程中,⾸先要对对象区域中的垃圾做标记;标记完成之后,就进⼊垃圾清理阶段
副垃圾回收 器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,所以这个复制过程,也 就相当于完成了内存整理操作,复制后空闲区域就没有内存碎⽚了。
主垃圾回收器
主垃圾回收器是采⽤标记清除
标记阶段就是从⼀组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
全停顿
V8是使⽤副垃圾回收器和主垃圾回收器处理垃圾回收的,不过由于JavaScript是运⾏在主线程 之上的,⼀旦执⾏垃圾回收算法,都需要将正在执⾏的JavaScript脚本暂停下来,待垃圾回收完毕后再恢复 脚本执⾏。我们把这种⾏为叫做全停顿(Stop-The-World)。

若是这样的时间花销,那么应⽤的性能和响应能⼒都会直线下降。
为了降低⽼⽣代的垃圾回收⽽造成的卡顿,V8将标记过程分为⼀个个的⼦标记过程,同时让垃圾回收标记 和JavaScript应⽤逻辑交替进⾏,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking)算法

使⽤增量标记算法,可以把⼀个完整的垃圾回收任务拆分为很多⼩的任务,这些⼩的任务执⾏时间⽐较短, 可以穿插在其他的JavaScript任务中间执⾏,这样当执⾏上述动画效果时,就不会让⽤⼾因为垃圾回收任务 ⽽感受到⻚⾯的卡顿了。