较全的垃圾回收算法及V8引擎的回收思想

一. 什么是垃圾、为什么要回收

二. 垃圾回收算法要做什么事情

三. 垃圾回收算法相关概念

四. 垃圾回收算法

4.1 标记-清除

4.2 引用计数

4.3 复制算法

4.4 标记整理

4.5 分代回收

五. 认识V8的垃圾回收

1. 认识V8

2. V8垃圾回收策略

3. V8如何回收新生代对象

4. V8如何回收老生代对象

5. 标记增量如何进行垃圾回收

一. 什么是垃圾、为什么要回收
1. 什么是垃圾

在程序执行的过程中,只要程序提出请求如定义变量、定义常量等,操作系统或运行时都会给我们分配内存空间。程序执行过程中会产生不再使用的变量、常量等,我们就称为垃圾。

2. 为什么要回收

如果不进行垃圾回收,我们的内存被占用情况将会越来越严重,导致后续的程序执行得不到足够的内存空间,轻则影响系统性能,重则导致程序崩溃。

3. 如何回收

js、java、python、等都是采用程序搭载了GC(垃圾回收)算法进行自动回收。从申请内存空间、使用内存空间、释放内存空间,GC算法会帮我们自动完成。

二. 垃圾回收算法要做什么事情
  1. 找到内存空间的垃圾
  2. 回收垃圾,让程序可以再次利用这部分空间
三. 垃圾回收算法相关概念
  1. 对象/头/域

    • 对象:通过应用程序创建的数据的集合,数据存储在内存空间里。对象由头Header和域field组成。
    • 头:保存对象本身信息的部分称之为头Header,如对象的大小、对象的种类,对象使用者无法更改头信息
    • 域:对象使用者在可访问的部分称之为域,个人理解就是对象的值。对象使用者可以更改域信息。域中数据包含指针和非指针类,非指针就是值本身(可以理解为基础数据类型)、指针指向内存中模块区域也就是其他对象的地址。
  2. 指针:指针是指对象的域中存储的是一个指向其他对象的地址
  3. 活动对象/非活动对象:从根出发,可以访问的是活动对象,否则是非活动对象
  4. 分配:当程序创建对象时,向分配器申请,分配器寻找到合适的符合要求的空间并将空间地址返回给程序
  5. 分块:初始状态下,堆被一个大的分块所占据。然后,程序会根据 应用程序的要求把这个分块分割成合适的大小,作为(活动)对象使用。活动对象不久后会转化为垃圾被回收。此时,这部分被回收的内存空间再次成为分块,为下次被利用做准备。也就是说,内存里的各个区块都重复着分块→活动对象→垃圾(非活动对象)→分块→ …… 这样的过程。
  6. 根:root,根是指向对象的指针的起点,浏览器环境是window,node环境是global
  7. 垃圾回收时应用程序处于暂停状态
  8. 堆与栈:
内存 数据结构
new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。由程序员分配和回收 是一棵完全二叉树结构
存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。 是一种连续存储的数据结构,特点是存储的数据先进后出
四. 垃圾回收算法
4.1 标记-清除

1. 标记清除Mark Sweep GC,1960年提出此概念,最早出现的GC算法。整个过程由标记+清除,两个阶段组成。

  • 标记阶段:从根出发遍历所有能够从根可达的对象,将其标记为活动对象
  • 清除阶段:将未标记的非活动对象进行统一回收

2. 什么是可达对象?

从根出发,如果是浏览器就是从window出发,如果是node则是从global出发,可以访问到的就是可达对象。

3. 遍历对象采用的什么算法?怎么实现遍历所有对象?

遍历对象可以采用两种算法,深度优先算法和广度优先算法

  • 深度优先算法:Depth-First-Search,简称DFS

主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底...,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。
较全的垃圾回收算法及V8引擎的回收思想_第1张图片

所以最终的执行顺序是:1-2-5-9-3-6-10-7-4-8

  • 广度优先算法:Breath First Search,简称BFS

主要思路是指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点。

较全的垃圾回收算法及V8引擎的回收思想_第2张图片

动态效果可参考如下链接:
https://s4.51cto.com/oss/202004/16/a2c7c61edcadffeed85c10f53f1c988c.gif

最终的执行顺序是1-2-3-4-5-6-7-8-9-10

广度优先算法采用队列实现

4. 缺点:

  • 效率问题:标记和清除环节因为需要找到所有可达对象,所以效率并不高
  • 空间碎片化:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,碎片过多会导致大对象无法分配到足够的连续内存,从而不得不提前触发GC,甚至导致应用程序暂停。

较全的垃圾回收算法及V8引擎的回收思想_第3张图片

4.2 引用计数
  1. 算法实现思路:通过在对象头中分配一个空间来记录该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被立刻回收。

示例:let a = 'abc',则abc的引用计数为1

当我们执行a=null时,abc的引用计数就变为了0,此时会立即触发GC

  1. 优点:
  • 即可回收垃圾:每个对象都有自己的计数器,一旦计数器为0立刻进行垃圾回收
  • 最大暂停时间短:因为垃圾的即时回收,会大幅缩减程序的暂停时间
  • 没必要沿指针查找:与标记清除算法不一样,没必要由根出发沿指针查找
  1. 缺点:
  • 计数器的增减比较繁重:
  • 计数器需要占用很多位:
  • 循环引用无法回收 a引用b,b引用a,这种情况始终无法回收
4.3 复制算法
  1. 算法实现思路:copying GC,把某个空间里的活动对象复制到其他空间,把原空间里的所有对象都回收掉。原空间称为From空间,粘贴活动对象的新空间称为To空间。当From空间完全被占满时,GC会将活动对象全部复制到To空间,当复制完成后,该算法会把From空间和To空间互换,GC也就结束了。From和To空间大小必须一致。
  2. 优点:
  • 不会发生碎片化:每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况
  • 优秀的吞吐量:只搜索从根出发的活动对象并只复制活动对象
  1. 缺点:
  • 堆使用效率低下:GC复制算法把堆分为对等的两份,通常只能利用其中的一半来安排对象。也就是只有一半堆被使用
  • 效率问题:在对象存活率较高时,复制操作次数多,效率降低;

较全的垃圾回收算法及V8引擎的回收思想_第4张图片

4.4 标记整理
  1. 算法实现思路

分为标记和整理两个阶段:首先标记出所有需要回收的对象,然后让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。不会立即进行垃圾回收

  1. 优点:不会产生空间碎片化
  2. 缺点:整理内存空间需要花费一定的时间

较全的垃圾回收算法及V8引擎的回收思想_第5张图片

4.5 分代回收
  1. 算法实现思路:

分代回收的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。

“分代收集”算法,把堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。

  1. 优点:因为针对不同的情况采用不同的GC算法,所以它的吞吐量得到了改善
  2. 缺点:在部分程序中会起到反作用,我们采用分代垃圾回收基于一个认知:很多对象年纪轻轻就称为了垃圾,如果程序中对象都活的很久,会造成两个问题(1)新生代GC花费的时间变多(2)老年代频繁GC
五. 认识V8的垃圾回
1. 认识V8
  1. v8是一款主流的js执行引擎
  2. v8采用即时编译(速度很快)
  3. v8内存设限:64位不超过1.5G,32位不超过800M

为什么会设置为1.5G和800M?V8官方进行过一个测试:1.5G的垃圾,采用增量标记算法需要50毫秒,如果采用的是非增量回收需要1秒,1秒的停顿客户已有感知,如果垃圾大于1.5G会影响用户体检,故而将内存设置为1.5G

2. V8垃圾回收策略

1. 采用分代回收的思想、将内存安装一定的规则分为新生代内存区、老生代内存区,针对不同的内存区采用不同的垃圾回收机制
较全的垃圾回收算法及V8引擎的回收思想_第6张图片

2. v8常用的GC算法:

  • 分代回收
  • 空间复制
  • 标记清除
  • 标记整理
  • 标记增量(为了提高效率)
3. V8如何回收新生代对象

007.png

1. 将内存分为大小相同的两块:新生代内存块中存放新生代对象也就是存活时间较短的对象(比如局部变量),老生代内存中存放老生代对象也就是存货时间教长的对象

2. 64位系统中新生代内存的大小为32M,32位系统中新生代内存大小为16M

3. 新生代对象采用的GC算法主要有复制算法+标记整理

4. 新生代缓存区:分为两个大小相同的区域from和to.

from:使用状态,在声明变量时都会进入到from空间,当from使用达到一定的程度触发GC,首先进行活动对象的标记,不可达对象不标记,然后进行整理,将不连续的内存空间整理为连续内存空间。然后将活动对象从from拷贝到to中,然后将from完全释放。

to: 空闲状态

较全的垃圾回收算法及V8引擎的回收思想_第7张图片

5. 拷贝过程中存在晋升:晋升就是将新生代内存区中的对象转移至老生代对象

出现晋升的时机有:
(1)一轮GC之后还存活的对象需要晋升

(2)to空间的使用率超过25%将所有对象转移至老生代空间,设置25%这个阈值的原因是当这次回收完成后,这个To空间会变为From空间,接下来的内存分配将在这个空间中进行。如果占比过高,会影响后续的内存分配

4. V8如何回收老生代对象
  • 64位系统中老生代内存的大小为1.4G,32位系统中新生代内存大小为700M
  • 老年代对象就是指存活时间比较长的对象如全局变量、闭包等
  • 老年代对象主要采用标记清除、标记整理、增量标记算法,主要使用标记清除,但会产生空间碎片化问题,即使这样,V8底层针对老年代对象也主要使用的是此种GC算法(效率高)。
  • 什么时候用标记整理?当我们想要把新生代存储区的内容挪到老生代存储区中,但老生代存储区的内存空间不能满足,此时就会进行一轮标记整理对碎片化空间进行整理,以释放更多的空间
5. 标记增量如果进行垃圾回收?

因为垃圾回收会导致程序暂停。将一整段回收操作分为多个阶段进行,以方便和程序交互进行。

较全的垃圾回收算法及V8引擎的回收思想_第8张图片

参考链接:cnblogs.com/xiaoyuxy/p/12258874.htm
参考书籍:《垃圾回收的算法与实现》

你可能感兴趣的:(javascript,垃圾回收机制,前端)