目录
概念
内存管理
开发者主动去申请、使用、释放内存空间。
JS中的垃圾
- 不再被引用的对象
- 不能从根上访问到的对象
可达对象
- 能被访问到的对象就是可达对象
- 可达的标准是从根出发是否能被找到
- JS中的根可以理解为全局变量对象,即全局执行上下文
GC
垃圾回收机制的简写,GC中的垃圾回收器的工作内容就是找到内存中的垃圾,并释放和回收空间。
- GC中的垃圾:
程序中不再需要使用的对象,和不能再访问到的对象 - GC算法:
查找垃圾和释放、回收空间所遵循的规则 - GC的目的:
实现内存空间的合理使用和良性循环
常见GC算法
- 引用计数
- 标记清除
- 标记整理
- 分代回收
引用计数算法
原理:
当对象的引用关系发生改变时,通过引用计数器修改当前对象的引用数值,当引用数为0时,就对当前的对象空间进行回收和释放。
优点:
- 发现垃圾立即回收
- 最大限度减少程序暂停/卡顿
缺点:
- 无法回收循环引用的对象
- 时间和资源开销大
标记清除算法
原理:
1.遍历所有对象,标记活动对象(可达对象)
2.遍历所有对象,清除没有标记的对象,并把上一轮遍历中作的标记清除掉
优点:
- 能够回收循环引用的对象
缺点:
- 空间碎片化,浪费空间
- 不会立即对垃圾对象进行回收
标记整理算法
原理:
- 标记清除算法的增强
- 标记阶段的操作和标记清除一致
- 在清除阶段会先进行整理,移动对象位置,使空间连续
优点:
- 减少碎片化空间
缺点:
- 不会立即回收垃圾对象
V8引擎
- 主流的JS执行引擎
- 采用即时编译,速度快
- 内存设限,32位操作系统上限为800M,64位操作系统为1.5G
V8垃圾回收策略:
分代回收思想
将内存分为新生代和老生代对象存储区,针对不同对象采用不同算法。
常用GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
V8内存分配
- V8内存空间一分为二
- 小空间用于存储新生代对象(32M|16M)
- 新生代指存活时间较短的对象(如局部作用域中的变量)
- 老生代存储区(1.4G|700M)
- 老生代对象指存活时间较长的对象,如全局作用域和闭包中的变量
V8回收新生代对象
算法:空间复制+标记整理
新生代存储区分为两个等大小空间,使用空间为From,空闲空间为To,活动对象存储在From空间,标记整理后将活动对象拷贝至To,然后From和To交换空间,From成为To,To成为From。
晋升
拷贝过程中,可能会出现晋升操作,也就是将新生代对象移动至老生代存储区。
需要晋升的情况:
- 一轮GC之后还存活的新生代对象
- 拷贝时To空间的使用率超过25%
V8回收老生代对象
算法:标记清除+标记整理+标记增量
主要使用标记清除完成垃圾空间的回收;当新生代对象需要晋升,但老生代存储区的空间不足时,采用标记整理进行空间优化;增量标记用来进行效率优化。
标记增量如何优化垃圾回收
垃圾回收时会阻塞JS程序的运行,标记增量将一段完整的垃圾回收操作进行分段处理,和JS程序交替进行,减少了程序被阻塞的时间。
与新生代对比
- 新生代区域垃圾回收牺牲空间(总有一个空闲空间被浪费)换时间
- 老生代区域空间大,变量多,不适合复制算法
Performance工具
常见的内存问题
- 页面延迟加载或卡顿-频繁GC
- 页面持续性出现糟糕的性能-内存膨胀
- 页面性能随着时间越来越差-内存泄漏
监控内存的几种方式
- 浏览器任务管理器:查看网页DOM内存和JS内存是否异常,但无法定位问题。
- Timeline时序图记录:通过控制台-性能工具,录制时序图。
- 堆快照查找分离DOM:通过控制台-内存工具,获取堆快照
- 分离DOM:从DOM树上脱离(页面中不显示),但依然被引用的DOM。
- 判断是否存在频繁GC:
- Timeline中频繁地上升下降
- 任务管理器中的内存频繁地增大减小
几种优化方式:
慎用全局变量
尽量使用局部变量
缓存全局变量
将使用中不可避免的全局变量缓存到局部
通过原型添加方法
在原型对象上新增实例对象需要的方法,而不是在构造函数中
避开闭包陷阱
闭包中产生的引用关系在不使用的时候要把它释放掉,使内存得以回收
避免属性访问方法的使用
直接通过对象属性名来访问对象的成员,而不是封装一个方法来访问。
function Person(){
this.name = 'Tom'
this.age = 18
this.getAge = function(){
return this.age
}
}
const p = new Person
const pAge = p.age
// const pAge = p.getAge()
For循环优化
const arr = [1, 3, 5, 7, 9]
for(let i=0, len=arr.length; i<len; i++){
console.log(arr[i])
}
采用最优循环方式
forEach > 优化后的for > for…in
文档碎片优化节点添加
document.createDocumentFragment()
克隆优化节点操作
domEle.cloneNode(false)
直接量(字面量)替换new Object
let arr = [1,2,3]
// 而不是
let arr2 = new Array(3)