浅谈垃圾回收机制

参考文献:
1.http://www.manongjc.com/detail/5-fwwvmtvwvmgzkiz.html
2.https://www.cnblogs.com/gg-qq/p/13862251.html
3.https://zhuanlan.zhihu.com/p/348988741

垃圾回收

我们在写js代码的时候,会频繁的操作数据。一些数据不需要的时候,就是垃圾数据,垃圾数据占用的内存就会被回收。
找到不再被使用的变量,然后释放其占用的内存,但这个过程不是时时的,因为其开销比较大,

所以垃圾回收器会按照固定时间间隔周期性的执行

变量的生命周期

比如:

let dog = new Object();
let dog.a = new Array(1);
  • 当js执行这段代码,会在全局添加dog属性,并且在堆中创建了一个空对象,将该对象的地址指向dog.
  • 随后又创建一个数组,并把属性的地址指向dog.a,如下图:

在这里插入图片描述
如果此时,我将另外一个对象赋给了 a 属性,代码如下所示:

dog.a = new Object()

此时的内存布局图:
  在这里插入图片描述
a 的指向改变了, 此时堆中的数组对象就成为了不被使用的数据,专业名词叫不可达的数据。

这就是需要回收的垃圾数据。

垃圾回收方式

标记清楚

当变量进入环境,被标记为进入,当变量离开环境,则标记为“离开”。标记离开的就回收内存。

引用计数(低版本浏览器)

变量声明,第一次赋值为1,然后当这个变量值改变,记录为0,将技术为0的收回。

内存泄漏

1.意外的全局变量引起的内存泄露

原因: 全局变量不会被回收

解决:使用严格模式避免

2.闭包引起的

原因: 活动对象被引用,使闭包内的变量不会被释放

解决: 将活动对象赋值为null

3.被清理的DOM元素的引用

原因: 虽然DOM被删掉了,但对象中还存在对DOM的引用

解决: 将对象赋值为null

4.被遗忘的定时器或回调

原因: 定时器内部实现闭包,回调也是闭包

解决: 清理定时器clearInterval、null

垃圾回收算法

1.标记空间可达值

v8采用的是可达性(reachability)算法来判断堆中的对象是不是应该被回收。
算法思路:

  • 从根节点除法,遍历所有对象。

  • 可以遍历到的对象,是可达的。

  • 没有遍历到的对象,是不可达的。
    浏览器环境下有很多根节点,如下:

  • 全局变量window,位于每个ifream中

  • DOM树

  • 存放在栈上的遍历

2. 回收【不可达】的内存

所有标记完成后,统一清理不可达的对象。

3. 做内存整理

频繁回收对象后,内存回出现大量不连续的空间,叫做内存碎片
当分配较大连续内存的时候,可能会出现空间不足,所以需要整理内存碎片。(可选,副垃圾回收器不会产生内存碎片)

4.分代收集(世代假说)

垃圾回收的策略是建立在代际假说之上,代际假说的2个特点如下:

  1. 大部分对象在内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得不可访问;
  2. 不死的对象会活得更久
    基于代际假说,V8 分别使用两个不同的垃圾回收器,以便更高效地实施垃圾回收

浏览器将数据分为两种,1.临时对象,2.长久对象。

临时对象:
  • 大部分对象在内存存活很短。
  • 函数内声明的遍历,或者会计作用域中的变量。当函数或者代码块执行结束,作用域中定义的变量就会被销毁。
  • 这类对象不可访问,可以马上回收。
长久对象:

生命周期长的对象,比如全局window,DOM,Web API等等。可以慢点回收。
针对于这两种有不同的回收策略,**V8把堆分成新生代(临时对象)和老生代(长久对象)**两个区域。
并且让副垃圾回收器负责新生代的垃圾回收,主垃圾回收器负责老生代的垃圾回收。
在这里插入图片描述

新生代 - 副垃圾回收器

新生区通常只支持 1~8M 的容量,使用Scavenge 算法来处理。

新生区主要存放内存占用小、对象存活时间短的对象。扫描频率更高一些。

新生区将内存划分为2部分,将对象都放入左侧对象区域,扫描时:

  1. 依次对对象区域中的垃圾做标记
  2. 将存活的对象复制到空闲区域中
  3. 清空对象区域
  4. 将原来的空闲区域变成了对象区域
老生代 - 主垃圾回收器

老生区占用大多数内存,同时也存放那些占用空间大、对象存活时间长的对象。

因为老生区通常都是较大的对象,采用新生代那样的赋值方式会花费大量的时间,导致执行效率低,同时还会浪费一半的内存空间。因而,主垃圾回收器是采用标记 - 清除(Mark-Sweep)的算法进行垃圾回收的:

  1. 从根元素递归变量所有对象,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据
  2. 清除垃圾数据,整理磁盘内存
    单线程的JavaScript在垃圾回收阶段将会阻塞线程,因此老生代的刷新频率低于新生代。
    在这里插入图片描述

除此之外,V8还使用了增量标记(Incremental Marking)算法,将清理全过程拆解为一个一个的小任务,避免造成页面卡顿、阻塞的情况。
在这里插入图片描述

对象晋升策略

一个对象,是被『分』给新生代,还是老生代?划分依据一个是占用内存大小,一个是存活时间。

一个比较小的对象,初始化被分为到新生代。如果经过两次垃圾回收依然还存活,我们就认定它是活跃对象,将其从新生代移动到老生代中。

磁盘整理

新生代与老生代在清理数据时都面临产生磁盘碎片的问题。

新生代会在移动过程中『顺手』存放在连续的内存中。而老生代就没那么容易了。

在这里插入图片描述

老生代清理掉红色的垃圾数据后,会产生大量不连续的内存碎片,而碎片过多会导致大对象无法分配到足够的连续内存,于是又产生了另外一种算法——标记 - 整理(Mark-Compact)

在这里插入图片描述

后续的清理,不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

浏览器中不同类型变量的内存都是何时释放?

Javascritp 中类型:值类型,引用类型。

  1. 引用类型:
    在没有引用之后,通过 V8 自动回收。

  2. 值类型:
    如果处于闭包的情况下,要等闭包没有引用才会被 V8 回收。
    非闭包的情况下,等待 V8 的新生代切换的时候回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值