从本文你将了解到
- 内存管理
- 垃圾回收与常见的 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
- 标记清除,标记整理,增量标记
- 增量标记是让垃圾回收和程序交替执行,而不是一次性执行垃圾回收
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。