垃圾回收机制

垃圾回收机制

GC是什么?

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

JS中的垃圾回收机制

JS垃圾回收机制

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

垃圾回收方式

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

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

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

www.zeeklog.com  - 垃圾回收机制


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

www.zeeklog.com  - 垃圾回收机制


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

  • 内存碎片化。空间内存块是不连续的,容易出现很多空闲内存块,还可能出现分配所需内存过大的对象时找不到合适的块。
  • 分配速度慢。每次分配内存都是一个O(n)的操作,分配效率慢。
www.zeeklog.com  - 垃圾回收机制
引用计数
  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结束。

www.zeeklog.com  - 垃圾回收机制

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

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

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

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

并行回收

全停顿:JS是单线程的,当进行垃圾回收时就会阻塞当前的JS脚本的执行,需等待垃圾回收完毕后再回复脚本执行,这种行为就是全停顿。如果某次GC时间过长,那么对用户来说就会造成页面卡顿的情况,所以有了并行回收。

www.zeeklog.com  - 垃圾回收机制


而并行回收就是使用多个辅助线程,与主线程同时进行垃圾回收,加快回收速度。但是主线程还是要让出。

并发回收

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

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

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

www.zeeklog.com  - 垃圾回收机制

哪些行为会引起内存泄漏

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

Read more

深入理解 Proxy 和 Object.defineProperty

在JavaScript中,对象是一种核心的数据结构,而对对象的操作也是开发中经常遇到的任务。在这个过程中,我们经常会使用到两个重要的特性:Proxy和Object.defineProperty。这两者都允许我们在对象上进行拦截和自定义操作,但它们在实现方式、应用场景和灵活性等方面存在一些显著的区别。本文将深入比较Proxy和Object.defineProperty,包括它们的基本概念、使用示例以及适用场景,以帮助读者更好地理解和运用这两个特性。 1. Object.defineProperty 1.1 基本概念 Object.defineProperty 是 ECMAScript 5 引入的一个方法,用于直接在对象上定义新属性或修改已有属性。它的基本语法如下: javascript 代码解读复制代码Object.defineProperty(obj, prop, descriptor); 其中,obj是目标对象,prop是要定义或修改的属性名,descriptor是一个描述符对象,用于定义属性的特性。 1.2 使用示例 javascript 代码解读复制代码//

By Ne0inhk

Proxy 和 Object.defineProperty 的区别

Proxy 和 Object.defineProperty 是 JavaScript 中两个不同的特性,它们的作用也不完全相同。 Object.defineProperty 允许你在一个对象上定义一个新属性或者修改一个已有属性。通过这个方法你可以精确地定义属性的特征,比如它是否可写、可枚举、可配置等。该方法的使用场景通常是需要在一个对象上创建一个属性,然后控制这个属性的行为。 Proxy 也可以用来代理一个对象,但是相比于 Object.defineProperty,它提供了更加强大的功能。使用 Proxy 可以截获并重定义对象的基本操作,比如访问属性、赋值、函数调用等等。在这些操作被执行之前,可以通过拦截器函数对这些操作进行拦截和修改。因此,通过 Proxy,你可以完全重写一个对象的默认行为。该方法的使用场景通常是需要对一个对象的行为进行定制化,或者需要在对象上添加额外的功能。 对比 以下是 Proxy 和 Object.defineProperty 的一些区别对比: 方面ProxyObject.defineProperty语法使用 new Proxy(target,

By Ne0inhk