内存管理(memory management)
概念
- 内存: 有可读写单元组成,表示一片可操作的空间
- 管理:人为操作去申请、使用和释放空间
- 内存管理:就是开发者申请、使用和释放内存空间
- 管理流程:申请-使用-释放
- Javascript中的内存管理: 1. 申请空间; 2. 使用空间; 3. 释放空间
//申请
let obj = {};
//使用
obj.name = "test";
//释放
obj = null;
Javascript中的垃圾回收
- javascript中的内存管理是自动的
- 什么被看作垃圾: 1.对象不再被引用,2.对象不能从根上被访问到
- 可达对象不会被看作垃圾: 1.递归可以访问到的对象就是可达对象, 2. 可达标准:从根出发可以被找到, 3. Javascript中的根可以理解为全局对象
GC算法
概念
- GC就是垃圾回收机制的简写
- GC可以找到内存中的垃圾,释放并回收空间
- 算法就是垃圾回收过程中所遵循的规则
常见GC算法
引用计数法
- 核心:
设置引用数,当引用关系改变时修改引用数,判断引用数,为0时释放回收内存空间
const user1 = {age:11}
const user2 = {age:22}
const user3 = {age:33}
const nameList = [user1.age,user2.age,user3.age]
// user1 user2 user3 仍然被引用 所以不会被回收
function fn(){
const num1 = 1;
const num2 = 2;
}
fn(); //在执行后 内部num1 num2 就会被回收
- 优点:
- 发现垃圾时立即回收
- 最大限度减少程序暂停
- 缺点:
- 无法回收循环引用的对象
- 时间开销大
标记清除算法
- 核心
分为标记和清除两个阶段
标记阶段: 遍历所有对象,标记活动对象,再次遍历,找到没有标记的对象,并且清除第一次的标记
清楚阶段: 回收没有标记对象所占空间
- 优点
可以回收循环引用对象
- 缺点
空间碎片化(内存空间释放后地址不连续)
不能立即回收垃圾
标记整理算法
- 核心
可以看作时标记清楚算法的增强
标记阶段: 和标记清楚算法的标记阶段相同
清楚阶段:先执行整理,移动对象位置使其紧邻
- 优点
解决空间碎片化
- 缺点
不能立即回收垃圾
V8
概念
- V8是Javascript中的一种执行引擎,在Node中也使用
- V8采用即时编译
- 内存空间设限(64位-1.5G, 32位-800M)
回收策略
- 采用分代回收的思想
- 内存分为新生代,老生代
- 针对不同对象采用不同算法
常用的GC算法
分代回收
空间复制
标记清楚
标记整理
标记增量
处理新生代对象
- 新生代对象: 存活时间较短的对象(局部变量)
- 处理前V8将空间一分为二,小空间(64位32M,32位16M)来存放新生代对象
- 回收过程
1. 回收采用空间复制和标记整理法
2. 先将新生代内存区等分为二(from/to)
3. from称为使用空间,to称为空闲空间
4. 活动对象放入from空间
5. 标记整理后将from空间中的活动对象拷贝到to
6. 然后to和from交换完成释放
- 需要注意
- 拷贝过程中可能出现晋升(晋升就是将新生代对象移动至老生代)
- 晋升条件: 一轮GC还存活的新生代需要晋升; To 空间的使用率超过25%
处理老生代对象
- 老年代对象:存活时间较长的对象
- 存放位置:在右侧老年代区域
- 大小限制: 64位操作系统1.4G, 32位操作系统700M
- 回收过程
- 主要采用标记清除,标记整理,增量标记算法
- 首先用标记清除完成垃圾空间的回收
- 采用标记整理进行空间优化
- 采用增量标记进行效率优化(因为垃圾回收会阻碍程序执行,此处程序执行和垃圾回收交替执行,既不会暂停很久,也可以回收垃圾,用户体验感好)
- 新生代区域和老年代区域细节对比
- 新生代区域垃圾回收使用空间换时间
- 老生代区域垃圾回收不适合复制算法
总结
- V8 是一款主流的JavaScript执行引擎
- V8 内存设置上限
- V8 采用基于分代回收思想实现垃圾回收
- V8 内存分为新生代和老生代
- V8 垃圾回收常见 GC 算法
Performance 工具
作用:
时刻监控内存的使用情况
何时使用
1.页面出现延迟加载或者经常性暂停
2.持续性糟糕体验
3.随着时间过长页面性能越来越差
监控内存的几种方式
1.浏览器任务管理器
2.timeline时序图记录
3.堆快照查找分离DOM
4.判断是否出现频繁垃圾回收
打开performance工具
1. 打开浏览器,输入地址
2. 打开开发者工具,选择性能
3.使用对应工具,用户操作,然后记录内存变化
下面是chrome的工具截图
下面是firefox的工具截图
一些概念
什么是分离Dom: 在界面上看不到,但是在JS代码中仍然引用,这种分离Dom会占用内存,GC无法回收,因此可以利用堆快照查找这些分离Dom
为什么要判断是否频繁GC: GC会导致程序暂停,频繁GC程序就会卡死,用户就会体验到卡顿
如何判断是否频繁GC: timeline内存频繁上升下降,浏览器任务管理器频繁增加减小
JS代码优化
可以将代码粘贴至网址去对比测试:https://jsperf.com/(本机访问失败)
优化点
-
慎用全局变量
全局变量存在于全局执行上下文,是所有作用域的顶端
全局变量一直存在于上下文执行栈中,直到程序结束
局部作用域中若存在和全局变量同名的情况,会污染全局变量
- 缓存全局变量
将无法避免使用的全局变量放入局部作用域中
- 通过原型对象添加方法,性能更优
- 避免闭包陷阱
- 避免属性访问方法的使用,性能更优
- for循环优化(将length的数值变为一个固定的值,如:var k= arr.length ; i < k; 性能更好)
- forEach, for, for-in循环比较
简单逻辑用ForEach, 然后可以用for, 最后考虑for-in
- 文档碎片优化节点添加
- 克隆优化节点
- 直接量替换New Object,性能更优
- 减少判断层级
- 减少作用域链查找层级
- 减少数据读取次数
- 字面量和构造式
- 减少循环体中的活动
- 减少声明和语句数
- 采用事件绑定
JSBench
比对两段代码性能的工具网站,网址:https://jsbench.me/
堆栈中的JS执行过程
JS代码的执行会在堆中放一个执行环境栈
先创建一个全局上下文,进行全局作用域的变量的存放: 基础类型放入栈中,引用类型放入堆中,函数的声明放入栈中,地址指向堆
函数的调用会单独开启一个上下文,执行完毕根据是否有闭包决定是否释放当前内存