从GC到JavaScript的性能优化

84 阅读5分钟

JavaScript内存管理

通过了解内存管理进行代码优化,提高代码运行性能、避免产生不必要的Bug。

内存管理介绍

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

JavaScript中的内存管理

ECMAscript中并没有提供内存管理相应的api供开发者主动调用。

但是它并不会脱离内存申请-使用-释放的基本管理流程。

// 申请
let obj = {}
// 使用
obj.name = 'laor'
// 间接方式释放
obj = null 

JavaScript垃圾回收

  • JavaScript中的内存管理是自动的,创建对象时会自动分配空间
  • 当对象不被引用时就认为它是垃圾
  • 对象不能从根上访问到时就是垃圾

找到垃圾,执行引擎对其进行清理,进行空间的释放和回收。

JavaScript中的可达对象

  • 可以访问到的对象就是可达对象(引用、作用域链)
  • 可达的标准就是从根出发是否能够被找到
  • JavaScript中的根可以理解为全局变量对象

JavaScript中的引用与可达

看一个栗子:

let obj = { name:'laor' }
// laor对象空间多了一个引用(引用计数)
let ali = obj
obj = null

首先从根上可以找到obj ,从而可以找到laor的对象空间,它是可达的

然后将obj赋值给ali, laor对象空间多了一个引用(引用计数)

obj赋值为null,此时laor对象空间仍然是可达的 ,因为ali还对其存在引用

稍微复杂一点的:

// 循环引用的例子
function objGroup(obj1,obj2){
    obj1.next = obj2
    obj2.pre = obj1
    return {
        o1:obj1,
        o2:obj2
    }
}
let obj = objGroup({name:'obj1'},{name:'obj2'})
console.log(obj)

delete obj.o1


delete obj.o2.pre

可达对象图示

cdd8e269-6c6e-44bd-9e85-e67a8372934a.webp

GC 算法介绍

GC定义与作用

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

GC中的垃圾是什么?

  • 程序中不再需要使用的对象
function fn() {
  name = 'lg' // 只从程序角度出发name是不需要了,不关心是否真的会被回收
  console.log(`${name} is a coder`)
}
fn() 
  • 程序中不能再访问到的对象
function fn() {
  const name = 'lg'
  console.log(`${name} is a coder`)
}
fn() 

GC算法是什么?

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

常见的GC算法

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

引用计数

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

优点

  • 发现垃圾时立即回收
  • 减少程序卡顿时间:当程序内存不足时立即将引用数为0的垃圾回收

缺点

  • 无法回收循环引用的对象
  • 资源消耗较大 ,因为要维护引用数值的变化,需要时刻监控对象的引用数值是否修改

标记清除

  • 核心思想:分标记和清除二个阶段完成
  • 遍历所有对象找标记活动对象(可达)
  • 遍历所有对象清除没有标记对象(不可达)
  • 回收相应的空间 10a34ecb-33cb-4a1f-a9e6-716af019fde8.webp

优点

  • 相对于引用计数,能够解决循环引用对象不能回收问题

缺点

  • 垃圾回收后会导致空间碎片化,空间地址不连续,浪费空间
  • 不会立即回收垃圾对象

标记整理

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

认识V8

  • V8是一款主流的JavaScript执行引擎
  • V8采用即时编译
  • V8内存设限:64位不超过1.5G,32位不超过800M

V8垃圾回收策略

  • 采用分代回收的思想

  • 内存分为新生代((32M/16M))和老生代(1.4G/700M)存储空间,小空间存储新生代对象(新生代指的是存活时间较短的对象) 03163634-c70e-47b5-92ea-2d16424ddefd.webp

  • 针对不同对象采取不同GC算法

V8常用GC算法

  • 分代回收
  • 空间复制
  • 标记清除
  • 标记整理
  • 标记增量

V8 回收新生代对象:空间换时间

  • 将新生代存储空间也一分为二,使用空间为From,空闲为To
  • 回收过程采用复制算法 + 标记整理:活动对象存储在From空间,标记整理后将活动对象拷贝至To
  • 将From空间完全释放From与To交换空间身份

注:经过一轮GC之后还活着的对象 以及 拷贝过程中To的使用率超过了25% ,会移动至老生代对象空间

V8 回收老生代对象

  • 主要采用标记清除,标记整理,增量标记算法

  • 首先使用标记清除完成垃圾空间的回收

  • 当把新生代的对象移往老生代的时候,当空间不足时,就会进行标记整理进行空间优化

  • 采用增量标记进行效率提升:当进行垃圾回收时会阻塞代码执行,所以将回收过程拆分成多段提高效率 bc4dfacf-9c84-41b1-9440-f400119316f9.webp

性能优化

内存问题

简单归类为

  • 内存泄漏:内存使用程序升高,无下降
  • 内存膨胀:应用程序本身设计需要较高的内存使用,在多数设备上都存在性能问题
  • 频繁垃圾回收:可以通过Performance面板内存变化图,浏览器任务管理器中JavaScript使用的内存变化分析

优化点

工具jsbenchjsperf

  • 慎用全局变量
  • 缓存全局变量 如 document
  • 通过原型新增方法,而不需放在构造函数内部
  • 合理使用闭包,谨防内存泄漏,DOM引用及时清除
  • For循环的优化,length进行提前保存,执行性能forEach > for > for in
  • 文档碎片优化DOM节点添加
  • 克隆优化节点操作