堆内存 栈内存

  • 基本类型变量名以及值都存放于栈内存

  • 引用类型变量值存放于堆内存,变量名以及堆内存地址存放于栈内存

V8 的内存分代

主要将内存分为新生代和老生代,新生代指的是那些存活时间较短的对象,老生代指的是存活时间较长的或者常驻内存的对象。

垃圾的来源

  1. 全局变量

    ​ 全局变量都不会是垃圾,因为该变量可能会被后面的代码所用

  2. 局部变量

    ​ 局部变量在函数执行完就会被回收

  3. window 上的变量

    ​ 与全局变量同理

  4. 对象 开辟的堆内存

  5. dom 节点 通过 getElement 等获取到的节点

垃圾的回收算法

javascript 的垃圾回收会对 javascript 执行线程形成阻塞

引用计数(已被大部分浏览器弃用)

使用计数器实时记录对象的引用计数,当计数为 0 时,即会被回收

var obj1 = {}; //此时开辟了一个堆内存,我们标记它{}为X,X的引用计数为1
var obj2 = obj1; //x的引用计数为2
obj1 = null; //x的引用计数为1
obj2 = null; //x的引用计数为0 此时会被回收
  1. 优点

    • 可即时回收垃圾
    • js 堵塞时间短
    • 无需沿着指针去一遍一遍找
  2. 缺点

    • 实现繁琐,每个赋值必须进行引用更新

    • 循环引用无法回收

      var obj1 = {}; //此时开辟了一个堆内存,我们标记它{}为X,X的引用计数为1
      var obj2 = {}; //此时开辟了一个堆内存,我们标记它{}为Y,Y的引用计数为1
      obj2.a = obj1; // Y: {a:X} X的引用计数为2 (内部引用)
      obj1.a = obj2; // X: {a:Y} Y的引用计数为2 (内部引用)
      //形成了环形引用
      obj1 = null; // X的引用计数为1
      obj2 = null; // Y的引用计数为1
      //此时X Y无法被释放

标记-清除(标记-整理-删除)(现代浏览器基本用这种)

定期的从这个全局对象(window||global)开始,递归查找引用的对象,在扫描过程中采用引用计数,如果能访问到,就表示活着,不会被释放,然后将散乱的可用内存进进行整理,使得到更多的连续可用内存

缺点:

  • 标记效率慢,垃圾回收每次都要去遍历检查每一个引用

优化点:

  • 针对新生代和老生代,采用不同的算法,老生代回收不用那么勤快,而新生代需要马上标记马上回收。
  • 增量执行(主要老生代):将回收过程分成许多一小份,垃圾回收与应用逻辑交替执行直到标记阶段完成。
  • 延迟清理:在 js 空余的时间再进行回收

算法:

Scanvenge(新生代)(无整理)

将新生代的内存一分为二,From 区和 To 区,用来存放对象的一半是 From 空间,处于闲置状态的一半是 To 空间。

image-20220712183142816

该算法缺点就是只能用一半的内存

新生代和老生代如何区分

对象刚创建时即为新生代,而新生代到老生代的过程称为晋升,晋升条件为:

  • 对象经历过一次 Scanvenge 算法仍存在
  • To 空间的内存占用超过限制

image-20220712182953307

那么老生代的定义可为:存活周期较长或常驻内存的对象,或为新生代对象回收中溢出的对象

Mark-Sweep&Mark-Compact(老生代) (标记—整理—清除)

老生代空间大,大部分都是活着的对象,GC 耗时比较长

垃圾回收时,并不会一分为二,首先是标记阶段。V8 会在标记阶段遍历老生代空间中的所有对象,并标记存活的对象(即还没有被完全释放的对象),Mark-Compact 将存货的整理到一边,在随后的清除阶段,会将所有未标记的老生代对象全部回收。

Mark-Sweep

image-20220712185556270

Mark-Compact

image-20220712185630412

三种回收算法对比

回收算法 Mark-Sweep Mark-Compact Scavenge
速度 中等 最慢 最快
空间开销 双倍空间(无碎片)
是否移动对象

内存泄露

即一些内存本来时没用的,但是无法被回收

  1. 没用全局变量
  2. 大量闭包

所以,我们要通过 delete 或重新赋值变量为 undefind 或者 null 来释放对象的引用。

WeekMap 和 WeekSet

顾名思义 Week 也就是说,是一种弱类型的 Map 和 Set,主要体现在垃圾回收和使用上。

使用

  • WeekMap 的 key 只能是objectnullundefine,不能是原始值。
  • WeekMap 不支持迭代以及 keys(),values() 和 entries() 方法,没有办法获取 WeakMap 的所有键或值。
    • 保留 get、set、delete、has
  • 同理 WeekSet 只能添加objectnullundefine,不能是原始值,不支持迭代,以及 size()和 keys()
    • 保留 add、has、delete

垃圾回收

WeekMap 和 WeekSet 都是弱引用,不会妨碍垃圾回收,也就是说存在其中的数据,在没有其它引用时,会及时被回收。

使用场景

  1. 保存节点等 object,防止内存泄露,及时进行垃圾回收

参考链接

  1. (21 条消息) 一文读懂 V8 垃圾回收机制——新生代 Scavenge、老生代 Mark-Sweep 和 Mark-Compact_俞华的博客-CSDN 博客_marksweepcompact

  2. JS 垃圾回收,这次可以看懂了(带图警告) - 掘金 (juejin.cn)