v8垃圾回收机制

133 阅读3分钟

栈和堆的分别是干嘛的

function foo(){
    const a = {a: 11, b: 22}
    const b = 2
    
    function tow() {
        const f = {a: 1, b: 2}
        const e = 2
    }
    tow()
}
foo()

以上代码产生了如下的栈和堆

image.png

  • 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]

假设现在进入一组数据需要占用两个位置的空间,我现在的数组已经没有两个连续的空间了, 所以放不下这个数据了

碎片空间顾名思义就是, 一些零散的空间, 散落在各处。