栈
- 定义
栈用来在函数执行时存储保存执行上下文环境,我们一般也称调用栈,如基本类型的变量,引用类型的引用地址等都保存在栈中。执行到当前函数时进行入栈,执行完毕进行出栈。 - 回收方式
有一个记录当前执行状态的指针(称为ESP
)指向活动栈,函数执行完毕,esp
下移到后一节点,销毁当前函数执行上下文。新的函数执行上下文入栈直接覆盖掉销毁的空间即可 .
function test() {
const a = { name: 'a' };
function showName() {
const b = { name: 'b' };
}
showName();
}
test();
堆
-
定义
对象的值等大数据保存在堆中。 -
回收方式
使用 JavaScript 中的垃圾回收器进行回收,通过标记的方式进行回收。
采用 可访问性(reachablility)算法来判断堆中的对象是否为活动对象。这个算法其实就将一些GC Root
作为初始存活对象的集合,从GC Root
对象触发,遍历GC Root
中的所有对象- 能够通过
GC Root
(全局 wimdow 、dom树、栈上的变量、回收非活动对象占据的内存)遍历到的对象会被认为是可访问的,我们将其标记为活动对象
,必须保留 - 如果一个对象无法通过 GC Root 遍历到,那么就认为这个对象是不可访问的,可能需要被回收,并标记为
非活动对象
。
- 能够通过
堆的垃圾回收
在垃圾回收领域有一个代际假说
的概念,大致意思就是数据分为生命周期短的和长久的。
根据代际假说,v8对堆的数据保存分为新生代区域
,老生代区域
两大块。这两区域的垃圾回收是不同的,分别为副垃圾回收器
和主垃圾回收器
。
-
新生代区域
存储空间小,不能存放大对象 -
老生代区域
空间大,存活时间长。新生代经过2次GC仍存活的的变量 -
副垃圾回收器
通过将新生代区域平分为2块区域:对象区
、空闲区
。当活动区
中内存满时启动垃圾回收,遍历对象进行垃圾标记,将活动
的对象复制到空闲区
,移动到空闲区的对象是按顺序来的,因此不用去整理。处理完后,将对象区
与空闲区
翻转,完成垃圾回收。复制操作需要时间成本,因此为了效率,空间一般设置的很小。空间小,容易填满,因此又采用了对象晋升策略
,经过两次垃圾回收依然存活的对象就会被移动到老生区 -
主垃圾回收器
采用标记-清除(Mark-Sweep)
的算法进行垃圾回收。解决标记-清除
空间不连续引入标记-整理(Mask-Compact)
,标记完后,先将所有存活的对象向一端移动,然后直接清理掉这一端以外的内存。
优化效率
-
并发标记
: 在主程序运行阶段,并发的对变量进行标记
处理,防止中断js主线程。笔辩主线程、辅助线程同时修改同一对象,需实现读写锁等功能。 -
并行整理
:进行整理时占用主线程,在此基础上,启动多个辅助线程一起处理,加快速度。 -
增量整理
:由于在整理时是占用的主线程,会阻塞js运行,因此通过增量的方式,将一次处理分成多个小任务处理,减小对用户的影响。js运行与标记相互交叉,防止变量引用在a,b之间变换,造成标记失败,使用黑白灰
三色标记法进行处理。 -
黑白灰
三色标记- 黑:节点以及子节点已处理完
- 灰:正在处理的节点。黑节点的子节点不能为白节点,直接置灰(写屏障)
- 白: 非活动对象,等待回收