JavaScript性能优化

696 阅读5分钟

概要

  • 内存管理
  • 垃圾回收与常见GC算法
  • V8引擎的垃圾回收
  • Performance工具
  • 代码优化实例

JavaScript 内存管理

内存为什么需要管理 ?

function fn() { arrList = []
arrList[100000] = 'lg is a coder' }
fn()

内存管理介绍

  • 内存:由可读单元组成,表示一片可操作性空间
  • 管理:人为地去操作一片空间的申请、使用和释放
  • 内存管理:开发者主动申请空间、使用空间、释放空间
  • 管理流程:申请-使用-释放

JavaScript中的内存管理

  • 申请内存空间
  • 使用内存空间
  • 释放内存空间

JavaScript中的垃圾回收

JavaScript中的垃圾

  • JavaScript中内存管理是自动的
  • 对象不再被引用时是垃圾
  • 对象不能从根上访问到是垃圾

JavaScript中的可达对象

  • 可以访问到的对象就是可达对象(引用、作用域链)
  • 可达的标准就是从根上出发是否能被够找到
  • JavaScript中的根就可以理解是全局变量对象
function objGroup(obj1, boj2) {
  obj1.next = obj2
  obj2.prev = obj1
  return {
    o1: obj1,
    o2: obj2
  }
}

let obj = objGroup({ name:'obj1' }, { name:'obj2' })

可达对象图示:

当改变关系如下,{name: 'obj1'} 就是不可达的了

function objGroup(obj1, boj2) {
  obj1.next = obj2
  obj2.prev = null
  return {
    o1: null,
    o2: obj2
  }
}

let obj = objGroup({ name:'obj1' }, { name:'obj2' })

可达对象图示:

GC 算法介绍

GC 定义与作用

  • GC 就是垃圾回收机制的简写
  • GC 可以找到内存中的垃圾、并释放和回收空间

GC 里的垃圾是什么

  • 程序中不再需要使用的对象
function func() {
  name = 'lg'
  return `${name} is a coder`
}
func()
  • 程序中不能再访问到的对象
function func() {
  const name = 'lg'
  return `${name} is a coder`
}
func()

GC 算法是什么

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

常见GC算法

  • 引用计数
  • 标记清除
  • 标记整理
  • 分代回收

引用计数算法实现原理

  • 核心思想:设置引用数,判断当前引用数是否为0
  • 引用计数器
  • 引用关系改变时修改引用数字
  • 引用计数为0是立即回收

引用计数算法优缺点

  • 优点
    • 发现垃圾时立即回收
    • 最大限度减少程序因内存消耗过大导致的暂停
  • 缺点
    • 无法回收循环引用的对象
    function fn() {
      const obj1 = {}
      const obj2 = {}
      obj1.name = obj2
      obj2.name = obj1
    }
    fn()
    // fn函数调用过后,由于obj1 obj2的引用计数不为0 无法回收
    
    • 时间开销大(需要监控、修改计数值)

标记清除算法实现原理

  • 核心思想:分标记和清除两个阶段完成
  • 遍历所有对象标记活动对象(可达对象)
  • 遍历所有对象清除没有标记的对象
  • 回收相应的空间

标记算法图示:

被标记的对象:A、B、C、D、E

未被标记的对象:a1, b1

标记算法优缺点

  • 优点
    • 相比引用计数来说,可回收循环引用的对象
  • 缺点
    • 回收的内存直接放在空闲链表,导致垃圾对象地址不连续

标记整理算法实现原理

  • 标记整理可以看做是标记清除的增强
  • 标记阶段的操作和标记清除一致
  • 清除阶段会先执行整理,移动对象位置,减少碎片化空间

标记清除算法图示

认识 V8

  • V8 是一款主流的JavaScript执行引擎(Chrome、Node等都采用)
  • V8 采用即时编译(直接将源码转成机器码)
  • V8 内存设限(为浏览器而设置,内存大小足够使用;回收时间合适)

V8 垃圾回收策略

  • 采用分代回收的思想
  • 内存分为新生代、老生代
  • 针对不同对象采用不同算法

垃圾回收策略图示

  • 常用 GC 算法
    • 分代回收
    • 空间复制
    • 标记清除
    • 标记整理
    • 标记增量

V8 如何回收新生代对象

V8 内存分配:

  • V8 内存空间一分为二
  • 小空间用于存储新生代对象(32M | 16M)
  • 新生代指的是存活时间较短的对象
新生代对象回收实现
  • 回收过程采用复制算法 + 标记整理
  • 新生代内存区分为两个等大小空间
  • 使用空间为From,空闲空间为To
  • 活动对象存储于From空间
  • 标记整理后将活动对象拷贝至To
  • From于To交换空间完成释放

回收细节说明:

  • 拷贝过程中可能出现晋升
  • 晋升就是将新生代对象移动至老生代
  • 一轮 GC 还存活的新生代对象需要晋升
  • To空间的使用超过25%

V8 如何回收老生代对象

老生代对象说明

  • 老生代对象存放在右侧老生代区域
  • 62位操作系统1.4G,32位操作系统700M
  • 老生代对象指的是存活时间较长的对象

老生代对象回收实现

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

细节对比

  • 新生代区域垃圾回收使用空间换时间
  • 老生代区域垃圾回收不适合复制算法

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

将垃圾回收分段进行,这样可以减少对程序运行的影响。

代码优化介绍

  • 慎用全局变量

  • 缓存全局变量

  • 通过原型新增方法

  • 避开闭包陷阱

  • 避免属性访问方法使用

  • 采用字面量替换New操作

  • 采用事件委托

  • 合并循环变量和条件