栈和堆的分别是干嘛的
function foo(){
const a = {a: 11, b: 22}
const b = 2
function tow() {
const f = {a: 1, b: 2}
const e = 2
}
tow()
}
foo()
以上代码产生了如下的栈和堆
- foo调用, 在栈中创建foo的执行上下文
- a = {a: 11, b: 22} 加入foo的执行上下文
- a的值是引用类型, 所以存入堆中, 分配一个引用地址
- b = 2 加入foo的执行上下文
- tow()执行, 在栈中创建tow的执行上下文
- f = {a: 1, b: 2} 加入tow的执行上下文
- f的值是引用类型, 所以存入堆中, 分配一个引用地址
- e = 2 加入tow的执行上下文
- tow()执行完成
- foo()执行完成
如何回收垃圾数据
栈中的数据如何回收
我来丰富一下上面的过程
- foo调用, 在栈中创建foo的执行上下文
- 指针指向 foo的位置
- a = {a: 11, b: 22} 加入foo的执行上下文
- a的值是引用类型, 所以存入堆中, 分配一个引用地址
- b = 2 加入foo的执行上下文
- tow()执行, 在栈中创建tow的执行上下文
- 指针指向 tow的位置
- f = {a: 1, b: 2} 加入tow的执行上下文
- f的值是引用类型, 所以存入堆中, 分配一个引用地址
- e = 2 加入tow的执行上下文
- tow()执行完成
- 指针指向 foo的位置
- foo()执行完成
- 指针指向 空
指针下方的都是存活内容, 指针上方的都是无用内容, 下次有新代码执行时, 直接覆盖无效内容区域即可
堆中的数据如何清理
- 堆分为两个区域, 假定 新区/老区
- 进入新区条件: 新进入且占用空间小的
- 进入老区条件: 新进入且占用空间大的, 在新区经历多次回收依然存活的
收回通常的流程
- 标记
- 清理
- 整理
新区回收
- 新区又平分为两个区域
- 数据只存放一个区域
- 回收时
- 遍历栈中有效数据区域
- 标记无用数据
- 将存活数据复制到另一个区域(直接复制到另一个区域, 也就不需要整理了)
- 重复上述, 两个区域来回倒
因为采用的是复制, 复制也比较耗时, 所以新区域通常比较小, 也解释了为什么占用空间大的数据,直接进入老区域
老区域收回
这个区域要么活的久要么数据量比较大, 数据大肯定不能采用复制的方法, 同时空间大,回收耗时可能会比较长,所以回收任务不是一次性完成, 是与js,页面渲染,等任务交叉完成。 好比一下代码, 执行100000次, 我们分批执行,每次调用一下定时器释放一下线程, 交给浏览器做其他事情
let i = 0
connst len = 100000
function a(){
for(let j=0;j<10;j++) {
i++
if(i<len){
setTimeout(a)
}
}
}
- 遍历栈中有效数据区域
- 标记数据
- 清理无用数据
- 多次清理后会照成“碎片空间”过多无法存储需要内存大的数据
- 所以回收机制就需要重新整理数据, 把空间整理成一大块一大块的连续空间
- 所以需要把数据朝着一个方向移动, 把多余的空间空出了
解释碎片空间
假设 我有个 10长度的空数组 a=[] a.length = 10,假设这个数据就是一个堆。
经过多次数据存储和清理后 a = [1, 2, empty, 3, empty, 4, empty, 5, empty, 6]
假设现在进入一组数据需要占用两个位置的空间,我现在的数组已经没有两个连续的空间了, 所以放不下这个数据了
碎片空间顾名思义就是, 一些零散的空间, 散落在各处。