性能优化主要相关模块
- 内存管理
- 垃圾回收与常见GC算法
- V8引擎的垃圾回收
- Performance工具
- 代码优化实例
1. 内存管理
JavaScript 内存管理 Memory Management
? 内存为什么需要管理
function fn() {
arrList = []
arrList[100000] = 'lg is a coder'
}
fn()
内存管理介绍
- 内存: 由可读写单元组成,表示一片可操作空间
- 管理:人为的去操作一片空间的申请、使用和释放
- 内存管理:开发者主动申请空间、使用空间、释放空间
- 管理流程: 申请-使用-释放
JavaScript 中的内存管理
- 申请内存空间
- 使用内存空间
- 释放内存空间
// 申请
let obj = {}
// 使用
obj.name = 'lg'
// 释放
obj = null
2. 垃圾回收与常见GC算法
2.1 垃圾回收
JavaScript 中的垃圾
- JavaScript中的内存管理是自动的
- 对象不再被引用时是垃圾
- 对象不能从根上访问到时 是垃圾
JavaScript 中的可达对象
- 可以访问到的对象就是可达对象 (引用、作用域链)
- 可达的标准就是从根出发是否能够被找到
- JavaScript 中的根就可以理解为是全局变量对象
JavaScript 中的引用和可达
let obj = {name: 'xm'} // 声明了一个xm的内存空间
let ali = obj // ali也引用了这个空间
obj = null // obj断开与xm的内存空间连接,但是ali还引用着
2.2 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算法
- 引用计数
- 标记清除
- 标记整理
- 分代回收
2.3 引用计数、标记清除、标记整理
1. 引用计数
引用计数算法实现原理
- 核心思想: 设置引入数,判断当前引用数是否为0
- 引用计数器
- 引用关系改变时修改引用数字
- 引用数字为0时立即回收
引用计数算法优点
- 发现垃圾时立即回收
- 最大限度减少程序暂停
引用计数算法缺点
- 无法回收循环引用的对象
- 时间开销大
2.标记清除
标记清除算法实现原理
- 核心思想:分标记和清除二个阶段完成
- 遍历所有对象找标记活动对象
- 遍历所有对象清除没有标记对象
(注意:在第二阶段当中,会把第一个阶段所设置的标记给抹掉,便于我们GC下次能正常工作)
- 回收相应的空间
标记清除算法图示
会把回收的空间直接放在一个叫做空闲列表上面,方便我们后续的程序可以直接在这儿可以申请使用
标记清除算法优点
*. 标记清除可以解决循环引用问题
标记清除算法缺点
- 空间的碎片化
由于当前所回收的垃圾对象在地址上它本身是不连续的,从而造成我们回收之后它们分散在各个角落,后续如果我们需要使用,刚好巧了,新的生成空间刚好与之匹配那可以直接用,一但多了或是少了就不太适合使用了。
// 任何的空间都会有两个部分
// 1. 头(存储这个空间的元信息 eg: 大小,地址 --- 称为头)
// 2. 域 (存放数据的)
// 如下图 假设一个方块是1个内存空间 左边蓝色的释放后得2个空间、右边蓝色的释放后得1个空间,在释放完成后,其实它们是分散的,也就是地址不连续。
// 地址不连续
// 如果下一次需要申请1.5的空间,左边多了,右边少了。 最大问题-空间碎片化
缺点图示
总结:相对引用计数来说,解决了循环引用问题,缺点则是造成空间碎片化,不能让空间得到最大化的使用
3. 标记整理
标记整理算法实现原理
- 标记整理可以看做是标记清除的增强
- 标记阶段的操作和标记清除一致
- 清除阶段会先执行整理,移动对象位置 -- 保证地址连续性
标记整理算法图示
2.2 GC算法总结
引用计数优缺点
- 优点:
- 可以即时回收垃圾对象
- 减少程序卡顿时间
- 缺点
- 无法回收循环引用的对象
- 资源消耗较大s
标记清除
- 优点:
- 可以回收循环引用的对象
- 缺点
- 空间产生碎片化空间,浪费空间
标记整理
- 优点:
- 减少碎片化空间
- 缺点:
- 不会立即回收垃圾对象
3. V8引擎
3.1 认识V8
- V8是一款主流的 JavaScript 执行引擎
- V8采用即时编译
- V8内存设限
即时编译: 之前很多的JavaScript引擎都需要将源代码转成字节码,然后再去执行。而对于V8来说,就可以直接将源码翻译成我们当前可以直接执行的机器码,所以这个时候的速度是非常快。
内存设限: 64位的操作系统下,上限是不超过1.5G,对于32位操作系统,上限是不超过800M。
?为什么有内存设限
- V8本身就是为了浏览器而制造的,现有的内存大小对于网页来说是足够使用了
- V8内部所去实现的垃圾回收机制,采用这个机制是合理的。
官方做个测试
当我们的垃圾内存达到1.5个G的时候,如果V8采用增量标记的算法进行垃圾回收,只需要消耗50毫秒,而如果采用非增量标记的算法进行回收,则需要1秒钟。那么从用户体验角度来说,1秒钟算很长的时间了,在这里就以1.5个G来为界了。
3.2 V8垃圾回收
V8垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代、老生代
- 针对不同对象采用不同算法
V8垃圾回收策略图示
V8中常用GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
V8内存分配
- V8内存空间一分为二
- 小空间用于存储新生代对象(32M | 16M)
- 新生代指的是存活时间较短的对象
什么叫存活时间较短 eg:局部的作用域,这个局部的作用域当中的变量在执行完成之后就肯定要去回收
如何回收新生代对象
新生代对象回收实现
- 回收过程采用复制算法 + 标记整理
- 新生代内存区分为二个等大小空间 (FROM、TO)
- 使用空间为From,空闲空间为To
- 活动对象存储于From空间
- 标记整理后将活动对象拷贝至To
- From与To交换空间完成释放
回收细节说明
- 拷贝过程中可能出现晋升
- 晋升就是将新生代对象移动至老生代
- 一轮GC还存活的新生代需要晋升
- To空间的使用率超过了25%
为什么是25%?
因为我们在将来进行回收操作的时候,最终是要吧From的空间和To的空间进行一个交换,也就是说以前的To会变成From,而From会变成To,也就意味着如果To使用率达到了80%,那么最终它变成活动对象存储空间后,那么新的对象好像就存不进去了
如何回收老生代对象
老生代对象说明
- 老生代对象存放在右侧老生代区域
- 64位操作系统1.4G,32操作系统700M
- 老生代对象就是指存活时间较长的对象
老生代对象回收实现
- 主要采用标记清除、标记整理增量标记算法
- 首先使用标记清除完成垃圾空间的回收
- 采用标记整理进行空间优化
- 采用增量标记进行效率优化
什么时候进行标记整理?
当把我们新生代区域里面的内容往我们当前这个老生代区域进行移动时,而且在这个时间节点上老生代区域的空间又不足以来存放我们新生代存储区所移过来的对象
细节对比
- 新生代区域垃圾回收使用空间换时间
- 老生代区域垃圾回收不适合复制算法
标记增量如何优化垃圾回收
增量标记算法:将一整段的垃圾回收操作,拆分成多个小步,组合完成整个垃圾回收操作。我们知道,当垃圾回收工作的时候,会阻塞JS程序执行,当我们需要优化垃圾回收的效率时,就可以使用增量标记算法。
优点:让垃圾回收与程序执行可以交替完成,让时间消耗更合理,达到效率优化的好处。
工作原理:
- JS 程序执行的过程中,会伴随着垃圾回收的工作
- 当垃圾回收工作时,需要遍历对象进行标记,此时不需要将所有对象进行标记,可以先将直接可达的对象进行标记,此时停下标记操作
- 然后让JS程序执行一会,之后,再让 GC 机制去做二步的标记操作,去标记那些间接可达的对象
- 重复以上两步,让程序执行和垃圾回收的标记操作交替执行,来达到优化效率和提升用户体验的目的
- 直到标记操作完成之后,最后执行垃圾回收
V8垃圾回收总结
- V8 是一款主流的JavaScript执行引擎
- V8 内存设置上限
- V8 采用基于分代回收思想实现垃圾回收
- V8 内存分为新生代和老生代
- V8 垃圾回收常见的GC算法