JavaScript性能优化【上】-- 内存管理、垃圾回收

本文详细介绍了JavaScript中的内存管理,包括为何需要管理、内存的申请与释放、垃圾回收的概念及常见算法如引用计数和标记清除。此外,特别讨论了V8引擎的垃圾回收策略,包括新生代和老生代对象的回收机制,以及如何通过分代回收、空间复制等优化内存利用率。
摘要由CSDN通过智能技术生成

JavaScript 内存管理

  • 内存为什么需要管理

      function fn () {
          arrList = []
              arrList[100000] = 'lg is a coder'
      }
      fn() // 内存泄露
    
  • 内存管理介绍
    内存:由可读写单元组成,表示一片可操作空间。
    管理:人为的去操作一片空间的申请、使用和释放。
    内存管理:开发者主动申请空间、使用空间、释放空间。
    管理流程:申请 – 使用 – 释放。

  • JavaScript中的内存管理

    1)申请内存空间

      let obj = {}
    

    2)使用内存空间

      obj.name = 'lg'
    

    3)释放内存空间

      obj = null
    

垃圾回收与常见 GC 算法

垃圾回收

  • JavaScript 中的垃圾
    1)JavaScript中内存管理是自动的;
    2)对象不再被引用时,会被当作垃圾;
    3)对象不能从根上访问到时,会被当作垃圾。

  • JavaScript 中的可达对象
    1)可以访问到的对象就是可达对象(引用、作用域链);
    2)可达的标准就是从根出发是否能够被找到;

    JavaScript 中的根就可以理解为是全局变量对象,也就是我们所说的全局执行上下文。

    总结
    JavaScript 中的垃圾回收,其实就是找到垃圾,然后让 JavaScript中的执行引擎进行空间的释放和回收。

  • JavaScript 中的引用和可达

    首先,来看一个简单的例子:

      let obj = {name: 'xm'}  // 此时,obj是可达的
      
      let ali = obj  // 发生引用数值变化
      
      obj = null    // 依然是可达对象,因为 ali 还在引用
    

    看完了上面的例子,我们对引用和可达有了简单的了解。下面,我们再来看一个稍微复杂的引用和可达的例子。

      function objGroup (obj1, obj2) {
          obj1.next = obj2
          obj2.prev = obj1
      
          return {
              o1: obj1,
              o2: obj2
          }
      }
      let obj = objGroup({name: 'obj1'}, {name: 'obj2'})
    

    下图表示的是上述代码的可达对象图:可达对象图
    图示分析
    首先,从全局的根开始出发,可以看到一个可达的对象 obj,它是通过函数调用之后,指向了一个内存空间,在这个空间中又包含了o1 和 o2两个成员,而在o1 和 o2的里面又通过相应的属性去指向了一个obj1的空间和一个obj2的空间,然后obj1 和 obj2 又通过next 和 prev 做了一个相互的引用,总之,我们所有的对象都可以从根上进行查找,无论是直接可达还是间接可达,都可以称为可达对象。

    下面,我们来看一个假设,我们使用delete语句将obj中o1的引用以及obj2对obj1的引用都delete掉,会发生什么样的改变呢?请看下图:
    在这里插入图片描述
    在上图中,当我们执行上面的delete操作时,将不可以再通过其他的方式找到 obj1这一对象空间,此时,它将会被认为是垃圾,由JavaScript引擎对其进行回收。

GC 算法

GC 算法概述

  • GC 定义与作用
    定义:
    GC 就是垃圾回收机制的简写。

    作用:
    GC 可以找到内存中的垃圾、并释放和回收空间。

  • GC 里的垃圾是什么

    1)程序中不再需要使用的对象。

    代码示例如下:

      function func () {
          name = 'lg'
          return `${name} is a coder`
      }
      func()
    

    2)程序中不能再访问到的对象。

    代码示例如下:

      // 程序运行过程中,变量是否还可以被引用到
      function func () {
          const name = 'lg'       // 函数调用结束,函数外部不能访问
          return `${name} is a coder`
      }
      func()
    
  • GC 算法是什么

    1)GC 是一种机制,垃圾回收器完成具体的工作;
    2)工作的内容就是查找垃圾释放空间、回收空间;
    3)算法就是工作时查找和回收所遵循的规则。

常见 GC 算法

引用计数算法
  • 实现原理
    1)核心思想:设置引用数,判断当前引用数是否为 0;
    2)使用 引用计数器 进行计数;
    3)引用关系改变时,引用计数器修改引用数字;
    4)引用数字为 0 时,立即回收。

    代码示例如下:

      const user1 = { age: 1 }
      const user2 = { age: 2 }
      
      const ageList = [user1.age, user2.age]
      
      function fn () {
          const num1 = 1
          const num2 = 2
      }	  
      fn()  
    

    在上述代码中,由于在ageList中,包含对user1和user2的引用,因此user1、user2的引用计数不是0,不会被回收。而在调用 fn函数执行结束后,外部将不能访问到 fn函数内的成员,此时的引用计数变为0,num1和num2所在的内存空间将会被回收掉。

  • 优缺点

    优点
    1)可以即时回收垃圾对象;
    2)当内存将要爆满时,会立即找到引用计数为0的对象空间进行释放,保证当前内存不会存在爆满的时候,因此可以最大限度减少程序暂停。

    缺点
    1)无法回收循环引用的对象;
    2)由于需要时刻监控每个对象的引用数字是否需要修改,所以时间开销会增大。

  • 问题

    对象之间的循环引用。

    代码示例如下:

      function fn () {
          const obj1 = {}
          const obj2 = {}
      
          obj1.name = obj2  
          obj2.name = obj1  
      
          return 'lg is a coder'
      }
      fn()
    

    在上面的代码中,obj1 和 obj2之间存在相互引用的情况,因此obj1 和 obj2之间的引用数字不为0,则会导致无法回收,造成内存浪费。总的来说,引用计数算法无法对这种对象之间的循环引用进行释放。

标记清除算法
  • 实现原理
    1)核心思想:分标记和清除二个阶段完成;
    2)遍历所有对象找标记活动对象;
    3)遍历所有对象清除没有标记对象;
    4)回收相应的空间。

下图是标记清除算法的实现原理图:
在这里插入图片描述
图示分析
从全局出发,可以发现 A、B、C三个可达对象,然后可以看到在 A 和 C的下面有一些值引用,这也正反映了标记清除算法的强大之处,即当对象的下面还有孩子,孩子的下面还有孩子的时候,这种时候将会使用递归的方式去寻找那些可达的对象。因此,在上面的D和E也会被进行可达的标记。但是右侧的a1和b1形成了单独的作用域,在左侧的链条中不会被找到,因此不会被标记,会被当作垃圾被JavaScript引擎回收。

  • 优缺点

    优点
    相对于引用计数来说,可以解决对象之间的循环引用不能回收的问题。

    缺点
    不会立即回收垃圾对象,因为它相对于之前的垃圾回收来说,会产生空间碎片化的问题,不能让我们的空间得到最大化的使用。

标记整理算法
  • 实现原理
    1)标记整理可以看做是标记清除的增强;
    2)标记阶段的操作和标记清除一致;
    3)清除阶段会先执行整理,移动对象位置。

    下面是标记整理算法的实现原理过程:
    在这里插入图片描述
    通过上图可以看到,在垃圾回收之前,整个空间分为被标记的活动对象、未被标记的非活动对象以及空闲空间。

    在这里插入图片描述
    然后,经过一系列的整理操作,会将别标记的活动对象整理在一处,方便JavaScript引擎进行回收。

    在这里插入图片描述
    最后,被标记的活动对象全部被整理放置在了空间的右边,然后JavaScript引擎会将右侧的标记区域进行回收,上图就显示了回收之后的效果。

  • 优缺点

    优点
    相对于标记清除来说,减少碎片化空间。

    缺点
    由于它需要先整理被标记的活动对象,所以不会立即回收垃圾对象。

V8 引擎的垃圾回收

  • 认识 V8
    1)V8是一款主流的 JavaScript 执行引擎;
    2)V8 速度快,采用即时编译;
    3)V8 内存设限,64位操作系统不超过1.5G,32位操作系统不超过800M。(针对浏览器,以及内存回收机制所决定)

  • V8 垃圾回收策略
    1)采用分代回收的思想;
    2)内存分为新生代、老生代;
    3)针对不同对象采用不同算法。

    下面是V8 垃圾回收的策略过程:

在这里插入图片描述

  • V8 中常用 GC 算法

    1)分代回收 – 内存设限
    2)空间复制
    3)标记清除
    4)标记整理
    5)标记增量

  • V8 内存分配

    内存分配图示如下:

    在这里插入图片描述
    图示分析
    1)V8 内存空间一分为二;
    2)小空间用于存储新生代对象(32M | 16M);
    3)新生代指的是存活时间较短的对象;

  • V8 如何回收新生代对象

    回收实现

    1)回收过程采用复制算法 + 标记整理;
    2)新生代内存区分为二个等大小空间;
    3)使用空间为From,空闲空间为 To;
    4)活动对象存储于 From 空间;
    5)标记整理后将活动对象拷贝至 To;
    6)From 与 To 交换空间完成释放。

    回收细节说明

    1)拷贝过程中可能出现晋升;
    2)晋升就是将新生代对象移动至老年代;
    3)一轮 GC 还存活的新生代需要晋升;
    4)To 空间的使用率超过25%,进行限制。

  • V8 如何回收老生代对象

    老生代对象说明

    1)老生代对象存放在右侧老生代区域;
    2)64位操作系统1.4G,32位操作系统700M;
    3)老生代对象就是指存活时间较长的对象,如在全局中定义的变量,闭包中的变量数据等。

    回收实现

    1)主要采用标记清除、标记整理、增量标记算法;
    2)首先使用标记清除完成垃圾空间的回收;
    3)采用标记整理进行空间优化;
    4)采用增量标记进行效率优化。

  • 新生代对象回收和老生代对象回收,细节对比

    1)新生代区域垃圾回收使用空间换时间;
    2)老生代区域垃圾回收不适合复制算法。

  • 标记增量如何优化垃圾回收

    标记增量实现过程,图示如下:

    在这里插入图片描述
    在上面的图示中,我们可以看到,垃圾回收和程序的运行是交替着实现的,这样不会导致很长时间的停顿,可以带来更好的用户体验。

    总结

    标记增量就是像上面展示的那样,将当前一整段的垃圾回收操作拆分成多个小段,组合着去完成当前的整个回收,从而去替代原来的一口气做完的垃圾回收操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值