垃圾回收(GC)
js中的垃圾回收也叫GC
.就是通过GC算法
找到内存中的垃圾,然后对这个垃圾空间进行释放与回收.方便后续代码继续使用.
js中的内存管理是自动的,每当我们去创建一个对象的时候会自动去分配内存空间,后面通过判断确认其是否是垃圾.
当垃圾回收工作的时候,它会阻塞js代码的执行
一.js中的垃圾有哪些?
不被引用或从根上不可达的就是垃圾
- 对象不再被
引用
是垃圾 - 对象不能
从根上访问
时是垃圾(局部作用域,闭包除外)- js中的根可以理解为
全局对象
,也叫全局上下文
- js中的根可以理解为
let obj = {name:'xm'} // 这里创建并使用了一个小明的内存空间,然后被obj引用了.而且这个空间能通过obj从全局被拿到
obj = null // 这里将obj与小明的引用断掉了,小明的空间没有其它引用所以无法从根访问到了,固然会被垃圾回收
- 程序中不再需要使用的对象
function func(){
name = 'lg'
return `${name} is a coder`
}
func() // 函数调用完成之后 name不再被使用
- 程序中不能再访问的对象
function func(){
const name = 'lg'
return `${name} is a coder`
}
func() // 函数调用完成之后 name不再被使用 且外部无法访问
二.GC算法
GC是一种机制,查找垃圾,释放,回收,分配空间时所遵循的规则叫GC算法
-
引用计数
设置引用数,判断当前引用数是否为0(如果为0就是垃圾).当引用关系发生改变时修改引用数字
优点: 发现垃圾立即回收,减少程序卡顿时间
缺点:无法回收循环引用的对象
,如下:首先fn中的obj1与obj2是无法在全局上下文中访问的,但是他们的引用数不为0,所以回收不了
function fn() { const obj1 = {} const obj2 = {} obj1.name = obj2 obj2.name = obj1 return 'xx' }
时间开销大
(需要时刻监听引用数的变化,而且需要数值也需要耗费时间)
-
标记清除
分两个阶段进行.遍历所有可达对象标记活动对象,不可达对象无法被标记. 遍历所有对象清除没有标记的对象,回收后的空间会放到空闲链表中.完成一次垃圾回收之后清除所有标记
优点: 可以回收循环引用的对象
缺点:- 容易产生
空间碎片化
.由于垃圾对象在地址上是不连续的,所以回收后的空间也不是连续的,造成后续在使用的时候就要匹配内存大小. - 不能立即回收垃圾对象,因为它要在最后才能清除
- 容易产生
-
标记整理
可以看做是标记清除的增强版,只不过在清除阶段会先执行整理内存空间,移动对象的位置让地址产生连续.
优点: 解决了空间碎片化的问题
缺点: 不能立即回收垃圾对象
-
V8垃圾回收(分代式回收机制)
三.认识V8
- V8是一款主流的JS执行引擎,在谷歌和node中都用的v8
- V8采用即时编译,所以很快
- V8内存设限 (本身是为浏览器而设计的,不需要太大的内存.在64位操作系统,内存在1.5G(这个内存下消耗的时间也才1秒) 32位操作系统,内存在800M.)
V8垃圾回收策略
- 基于V8内存设限,采用分代回收的思想(V8内存空间一分为二,小空间存储新生代,大空间存储老生代).针对不同对象采用不同的算法
- 按内存分为新生代和老生代
- 新生代(64位32M,32位16M,因为分配的内存比较小,所以采用一分为二的方法,用空间换时间)
- 指的是
存活时间较短
的对象(比如局部作用域中的变量) - 回收过程采用复制算法和标记整理
- 内存区分为两个相同大小的空间(From,To)
- 使用空间为From,所有活动对象存储在From空间.To是空闲空间
- 当From空间应用到一定程度之后就会触发GC操作,将活动对象进行整理拷贝至空闲空间To(也叫
翻转
).这样新生代就完成了空间的释放与回收操作
- 指的是
- 老生代(64位1.4G,32位700M,内存相对比较大不需要像新生代一样一分为二)
- 指的是
存活时间较长
的对象(比如全局作用域或闭包中的变量) - 当一个对象经过多次复制依然存活时,它将会被认为是生命周期较长的对象。
这种较长生命周期的对象随后会被移动到老生代中,采用新的算法进行管理。
对象从新生代中移动到老生代中的过程称为晋升。 晋升条件
:
对象晋升的条件主要有两个。
1、对象在新生代期间是否经历过算法回收;
2、To空间的内存占用比超过限制(To空间内存消耗是否超过25%,如果超过对象直接晋升
)- 回收过程主要采用标记清除,当新生代移过来而老生代空间不够用时会采用
标记整理
,最后采用增量标记
进行效率优化
- 指的是
标记增量如何优化垃圾回收?
让gc操作与代码交替运行,解决长时间阻塞问题
界定内存问题的标准
- 内存泄露:内存使用持续升高
- 内存膨胀(浏览器为了满足代码需求,主动申请的内存. 也有可能是设备配置的问题,带动不了. 这里要定位是设备问题还是代码问题)
- 频繁的垃圾回收(让用户感觉卡顿):Timeline中频繁的上升下降,任务管理器中数据频繁的增加减小
内存监控
- 浏览器任务管理器
- Timeline时序图记录
- 对快照查找分离DOM(分离DOM是一种内存泄露)
V8引擎工作流程(解析和编译js代码)
V8只是浏览器渲染引擎里执行js代码的一个组成部分
-
Scanner
词法分析器
是一个Js代码的扫描器,用来对js代码进行词法分析
,它会把代码分析成不同的tokens
.如下
-
Parser
全解析
- 解析被使用的代码
- 生成AST
- 构建具体scopes信息,变量引用,声明等
- 抛出所有语法错误
会将词法分析之后的tokens
解析成AST语法树
,同时在分析中对语法进行校验,如果有语法错误就会抛出错误.
-
Preparser
预解析
- 跳过未被使用的代码
- 不生成AST,但是会创建作用域信息
- 依据规范抛出特定错误
- 解析速度更快
-
Ignition
解释器
将生成的AST语法树
转为字节码
-
TurboFan
编译器
把字节码
转为汇编代码
(机器码),之后的话就可以开始代码的执行了
如何优化JS代码
在构造函数中声明对象属性
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
var p1 = new Point(11, 22); // hidden class Point created
var p2 = new Point(33, 44);
p1.z = 55; // another hidden class Point created
更改对象属性会产生新的隐藏类。正如你所见,p1和p2现在有不同的隐藏类了。这阻碍了TurboFan的优化尝试:具体来说,任何接受Point对象的方法现在都是去优化的。