垃圾回收机制

GC是什么?

GCGarbage Collection,程序工作过程中会产生很多垃圾,这些垃圾是程序不用的内存或者是之前使用过,以后不在会使用的内存空间,而GC负责回收这些垃圾的。

JS中的垃圾回收机制

JS垃圾回收机制

  • JS具有自动回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
  • JS有全局变量和局部变量。 全局变量会一直保存在内存中,直到页面卸载才回收变量内存;局部变量声明在函数内部,会在函数执行结束后回收内存
  • 当使用闭包时,函数内部定义的局部变量会一直留在内存中,不会被使用。 所以尽量避免使用闭包,以免造成内存泄漏

垃圾回收方式

标记清除
  • 当变量进行执行环境时,就标记这个变量进入环境,被标记为进入环境的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为离开环境,被标记为离开环境的变量会被内存释放。
  • 垃圾收集器在运行时会给存储在内存中的所有变量加上标记。然后,它会去掉环境中的变量和被环境中的变量引用的标记,剩下的变量将被视为需要删除的变量,垃圾收集器完成内存清除工作,销毁那些带有标记的值并回收他们所占用的内存。

标记清除法的优点
实现简单

标记清除法的缺点
在清除之后,剩余的对象内存位置是不变的,会导致内存空间不是连续的,出现了内存碎片。并且由于剩余空间内存不是一整块,它是由不同大小内存组成的内存列表,这就牵扯出了内存分配问题

image
假设我们需要为新建的对象分配大小为N的空间,由于当前空闲的内存是间断的,不连续的,则需要对空闲内存列表进行一次单向的遍历,找出内存空间大于等于N的块,才能为其分配内存空间。

image
标记清除算法有两个明显的缺点:

  • 内存碎片化。空间内存块是不连续的,容易出现很多空闲内存块,还可能出现分配所需内存过大的对象时找不到合适的块。
  • 分配速度慢。每次分配内存都是一个O(n)的操作,分配效率慢。

image

引用计数
  1. 引用计数就是跟踪记录每个值被引用的次数,当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1;
  2. 如果同一个值又被赋值给另一个变量,那该值的引用次数加1;
  3. 相反,如果包含这个值引用的变量又取得另一个值,则这个值的引用次数就减1;
  4. 当这个引用次数变为0,说明这个变量就没有用了,在下次垃圾回收器下次运行时会回收内存。

引用计数会引起一个问题:循环引用。例如:

function fun(){
  let obj1 = {}
  let obj2 = {}
  obj1.a = obj2   // obj1引用obj2
  obj2.a = obj1   // obj2引用obj1
}

在上面的例子中,obj1和obj2相互引用,两个对象的引用次数都为2.当函数执行完后,两个对象离开作用域,但obj1和obj2的引用次数还是2,不会减为0,这样就不会被回收。

解决方式就是:手动释放内存

obj1.a = null
obj2.a = null

减少垃圾回收

虽然浏览器可以进行自动垃圾回收,但当代码比较复杂时,垃圾回收的代价比较大,所以应该尽量减少垃圾回收。

  • 对数组进行优化:在清空一个数组时,将其赋值为[]
  • 对object进行优化:对象尽量复用,对于不再使用的对象,赋值为null
  • 对函数进行优化:在循环中的函数表达式,如果可以复用,尽量放在函数外部

浏览器 V8引擎的垃圾回收机制

V8的垃圾回收机制是怎样的

V8采用了分布式垃圾回收机制,将内存分为新生代和老生代两个部分。

新生代算法

新生代中的对象一般存活时间较短,使用Scavenge GC算法。Csavenge GC算法具体实现中,主要采用了一种复制式的方法,即Cheneny算法

在新生代空间中,Cheney算法将内存空间分为两部分,别分为From空间和To空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入From空间中,当From空间被占满时,新生代GC就会启动了。算法会检查From空间中存活的对象并复制到To空间中,如果有失活的对象就会销毁。当复制完成后From空间和To空间互换,GC结束。

image

有两种情况会使新生代中的对象移到老生代中:

  1. 当一个对象经过2次复制后依然存活,它将会被认为是生命周期较长的对象,随后会被移动到老生代中,采用老生代的垃圾回收策略进行管理。
  2. 如果复制一个对象到空闲区时,空闲区空间占用超过了25%,那么这个对象会被直接晋升到老生代空间中。设置25%的原因是当完成Scanvenge回收后,空闲区将翻转成使用区,继续进行对象内存的分配,若占比过大,将影响后续的内存分配
老生代算法

老生代中的对象一般存活时间较长且占用空间大,因为老生代中的对象通常比较大,如果再用新生代赋值的方法就会非常耗时,从而导致回收执行效率不高。老生代使用了两个算法,分别是标记清除算法和标记压缩算法。

标记清除算法和浏览器回收机制的标记清除算法相同。

并行回收

全停顿:JS是单线程的,当进行垃圾回收时就会阻塞当前的JS脚本的执行,需等待垃圾回收完毕后再回复脚本执行,这种行为就是全停顿。如果某次GC时间过长,那么对用户来说就会造成页面卡顿的情况,所以有了并行回收。
image
而并行回收就是使用多个辅助线程,与主线程同时进行垃圾回收,加快回收速度。但是主线程还是要让出。

并发回收

并行回收依然可能阻塞主线程,而是用并发回收,辅助线程可以在后台完成执行垃圾回收的操作,主线程也可以自由执行不被挂起。

并发回收的缺点:在一边进行垃圾回收一边执行JS时,堆中的对象引用关系随时都会发生变化,辅助线程之前做的一些标记或者正在进行的标记就会发生改变,所以需要额外的锁来进行限制

标记压缩算法
标记压缩算法可以有效解决标记清除法的缺点。在标记结束后,标记压缩算法会将活着的对象向内存的一端移动,最后清理掉边界的内存。

image

哪些行为会引起内存泄漏

  1. 意外的全局变量:在js中未对变量进行声明直接赋值的话,该变量会被当作全局变量。如果有大量的变量没有声明,会出现大量的全局变量,导致内存泄漏。
  2. 闭包:在使用闭包后,可以使我们访问到函数内部的变量和函数,当函数执行完毕后,这些变量会被保留在内存中,仍然可以使用,不会被回收。所以如果大量使用闭包,会导致内存泄漏。
  3. 引用了DOM元素,删除了DOM元素后,该引用还被保留在内存中没被删除
  4. 设置了setInterval定时器但忘记取消它:设置了 setInterval 定时器,而忘记取消它,如果在定时器中有循环函数有对外部变量的引用的话,那么这个变量会被一直保留在内存中,无法被回收。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值