从本文你将了解到

  • 内存管理
  • 垃圾回收与常见的 GC算法
  • V8引擎的垃圾回收

JS性能优化是提高运行效率,降低运行开销的行为

前端性能优化无处不在,例如请求资源时的网络,数据的传输方式,开发时使用的框架等

js内存管理
为什么需要管理(如图查看内存占用情况)

内存:由可读写单元组成,表示一片可操作空间

管理:人为的去操作一片空间的申请,使用和释放

内存管理:开发者主动申请空间,使用空间,释放空间

流程:申请-使用-释放

由于es中没有提供相应的内存管理API,所以JS不能像c,c++一样由开发者主动调用相应API完成空间管理

//申请(js执行引擎遇到变量定义语句时自动分配内存空间)
let obj = {}

//使用
obj.name = "mcgee"

//释放
obj = null

js中的垃圾回收

js中的内存管理是自动的

  • 对象不再被引用时是垃圾
  • 对象不能从根上访问到时是垃圾

js中的可达对象

可以访问到的对象就是可达对象(引用,作用域链)

可达的标准就是从根出发是否能够被找到

js中的根就可以理解为全局变量对象

//mcgee对象空间被nm引用。在全局执行上下文下,nm是可达的对象,间接意味着mcgee对象空间也是可达的
let nm = {name:"mcgee"}
//对象空间又多了一层引用
let ali = nm
//去除nm对对象的引用
nm = null
function objGroup(obj1,obj2){
  obj1.next = obj2
  obj2.prev = obj1

  return {
    o1:obj1,
    o2:obj2
  }
}

const result = objGroup({name:"obj1"},{name:"obj2"})
console.log(result)

如果我要删除obj1 图解上述代码

GC算法

垃圾回收机制的简写

GC可以找到内存中的垃圾,并释放和回收空间

GC是一种机制,垃圾回收器完成具体的工作,工作的内容就是查找垃圾释放空间,回收空间

算法就是工作时查找和回收所遵循的规则

  • 引用计数
  • 标记清除
  • 标记整理
  • 分代回收
//程序中不再需要使用的对象 (fn方法)
function fn(){
  name = "aa"
  return name
}
fn()

//程序中不能再访问到的对象 (name)
function fn1(){
  const name = "aa"
  return name
}
fn1()
  • 引用计数算法

设置引用数,判断当前引用数是否为0

引用计数器:GC通过判断引用计数器是否为0判断是否需要内存回收

优缺点:

  • 优点:发现垃圾时立即回收,最大限度的减少程序的暂停
  • 缺点:无法回收循环引用对象,时间开销大
function fn2(){
  const name = "aa"
  return name
}
fn2()//执行完后,函数内name变量的引用计数变成0,fn2所占的内存被回收释放

const o = {
  age:19
}
const ls = o.age //o的对象在ls处有引用,o在内存无法被释放

//obj1和obj2循环引用
function objGroup(obj1,obj2){
  obj1.next = obj2
  obj2.prev = obj1

  return {
    o1:obj1,
    o2:obj2
  }
}
  • 标记清除算法

将垃圾回收操作分成两个阶段 标记阶段和清除阶段

  • 标记阶段:遍历所有对象(递归查找)给活动对象标记
  • 清除阶段:遍历所有的对象清除没有标记对象(看图内部可能有a1,b1未被标记的私有变量),同时清除所有标记

优缺点:

  • 优点:解决对象循环引用的回收操作
  • 缺点1:空间碎片化(看图) 空间地址(分两部分,头和域) 头存元信息(大小地址) 域存放数据。蓝色的内存空间很碎,回收的空间不集中。
  • 缺点2:不会立即回收垃圾对象

当释放的空间内存地址不连续,导致分散再空闲列表的角落,导致下次再使用内存不太适合再使用
  • 标记整理算法

标记清除的增强操作,标记阶段与标记清除算法一致

整理阶段:会在清除之前,先执行整理,移动对象,使释放的地址连续,再执行清除操作

优缺点:

  • 优点:减少碎片化空间
  • 缺点:不会立即回收垃圾对象

V8引擎介绍

  • 主流JS执行引擎(浏览器,Node)
  • V8采用即时编译(其他js引擎需要将源代码转化成字节码,然后再执行,而V8将源码翻译成可直接执行的机器码)
  • V8内存设限 64x 1.5G | 32x 800M
  • 为什么设限:本身为浏览器设置的,针对于外部应用来说这样的内存大小是足够使用的,由内部的垃圾回收机制决定,如果内存再大一些,回收时间可能超过用户的感知
  • V8垃圾回收策略

    • 采用分代回收的思想
    • 内存分为新生代,老生代
    • 针对不同对象(代)采用不同算法(如图)

V8中常用GC算法

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

V8内存分配(如图)

  • V8内存空间一分为二,新生代对象和老生代对象
  • 小空间(新生代)用于存储新生代对象(64x 32M|32x 16M)
  • 新生代指的是存活时间较短的对象,(私有变量)

新生代对象回收实现

  • 回收过程采用复制算法+标记整理
  • 新生代内存区分为两个等大小空间
  • 使用空间为From,空闲空间为To
  • 活动对象存储于From空间
  • 触发GC机制后,标记整理From内的活动对象,后将活动对象拷贝至To
  • From与To交换空间完成释放

回收细节说明

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

V8如何回收老生代对象

  • 老生代对象对象说明
  • 老生代对象存放在右侧老生代区域(64x 1.4G | 32x 700M)
  • 老生代对象就是指存活时间较长的对象(闭包,全局变量)

老年代对象回收实现

  • 主要采用标记清除,标记整理,增量标记算法
  • 首先使用标记清除完成垃圾空间的回收
  • 当新生代内存向老生代区域移动的时候,且老生代存储区空间又不足存放新生代存储区所移过来的对象(也就是晋升),采用标记整理进行空间优化
  • 采用增量标记的方式进行效率优化

增量标记垃圾回收(如图)

  • 垃圾回收工作时会阻塞js的执行
  • 让垃圾回收和程序交替执行,而不是一次性执行垃圾回收
  • 先找到第一层可达对象标记一轮,然后执行GC回收,再递归第二层可达函数,再进行GC回收

新老生代对比

  • 新生代区域垃圾回收使用空间换时间
  • (因为它采用复制算法,每时每刻都有空闲空间存在,但是由于新生代空间本身就很小,分出来的空间就更小,所以空间上的浪费相对于时间上的提升是微不足道的)
  • 老生代区域垃圾回收不适合复制算法
  • (空间大,复制几百M空间,会有空间浪费,且存放的数据较多,复制消耗时间过大)

总结

  • v8主流js执行引擎
  • v8内存设置上限
  • v8采用基于分代回收思想实现垃圾回收
  • v8内存分为新生代和老生代
  • v8垃圾回收常见的GC算法(新生代复制+标记整理,老生代标记清除,标记整理,增量标记)

个人总结

  • 内存,可读写单元组成的操作空间
  • 内存管理,申请使用释放
  • js里没有处理内存的API,内存管理是自动的
  • 通过js垃圾回收进行自动处理
  • 处理对象是没有引用的对象,从根(全局对象)出发访问不到的对象
  • GC机制是查找垃圾,释放空间,回收空间
  • 算法有 引用计数算法,标记清除, 标记整理, 分代回收
  • V8引擎
  • 主流浏览器引擎
  • 采用及时编译
  • 内存设限1.5g 800m
  • V8采用分代GC回收思想
  • 将内存分为新生代区域和老生代区域,不同区域不同处理
  • 新生代gc
  • 新生代区域内存 32m,16m
  • 新生代gc回收 复制算法+标记整理
  • 新生代分为from+to两个空间
  • 活动对象都在from里,to里是空闲空间
  • 触发gc时,会堆from内的对象进行标记整理(两次for)
  • 然后from,to交换空间进行释放
  • 在执行过程中,一轮gc后还存活(还有引用的要晋升(将新生代对象移至老生代空间))
  • 在执行过程中,to空间使用率超25%要晋升
  • 老生代gc
  • 标记清除,标记整理,增量标记
  • 增量标记是让垃圾回收和程序交替执行,而不是一次性执行垃圾回收

mcgee0731
60 声望4 粉丝

不会做饭的程序猿不是一个好厨子