内存
1. V8 分代内存回收
两种内存
-
栈内存
- 主要存放两种内容
- 基本类型
- 引用类型的内存地址
- 主要存放两种内容
-
堆内存
- 主要存放 js 对象
- Node.js 对内存的 1.4GB 上限,就是指堆内存
回收方法
-
引用计数法
- 标记每个变量被引用的次数,一旦减少到 0,代表成为孤儿,可以回收
- 缺点:无法解决循环引用问题,永远不会减少到 0
-
标记清除法
- 内存中变量之间会有关联,对象之间互相引用,会形成一棵以 window 对象作为根的树
- 事件监听,DOM 对象,BOM 对象也可以作为根
- 在堆中但不在树中的,称为不可达对象,就可以回收
- 缺点:回收后,堆中会生成大量可用的碎片内存
- 内存中变量之间会有关联,对象之间互相引用,会形成一棵以 window 对象作为根的树
V8 的分代回收
-
把内存分为两部分
- 新生代
- 新对象刚开始都会在这里面
- 使用 Scavenge 复制算法
- 把新生代内存分为两半
- 一边是 From 空间,用于存放对象
- 另一边是 To 空间,处于空闲状态
- 当 From 空间不足时,会启动垃圾回收算法,把还存活的复制到 To 空间,复制完成后,两边角色互换
- 把新生代内存分为两半
- 需要结合晋升机制,把长时间存活的对象放到一个更大更高效的空间
- 晋升条件(任意一个)
- 对象已经历过一次垃圾回收
- 在 To 空间的占用超过 25%
- 晋升条件(任意一个)
- 老生代
- 接收可能长时间存活的对象
- 使用
- 标记清除法
- 日常标记出存活的对象,然后把其他清除
- 标记压缩法
- 当出现大量内存碎片,导致无法完整分配一块空间时,会启动这个算法,把存活对象向内存一端移动,执行速度较慢
- 标记清除法
- 新生代
-
导致的问题
- 全停顿
- 垃圾回收时,会阻塞 JavaScript 的执行,这叫全停顿
- 为了减少全停顿带来的体验问题,V8 引入了增量标记、延迟清理等方法,把回收过程变成可拆分和停顿的
- 全停顿
2. 内存泄漏
-
定义
- 应该被回收的空间无法被回收,导致内存不断被占用,可用内存越来越少
-
为什么会影响性能?
- 内存总是不够,导致垃圾回收频繁触发,产生全停顿
- 内存中存在大量对象,导致垃圾回收算法执行变慢
-
原因
- 一句话:被根节点误持有
- 分情况
- 把临时变量不断挂载到 window 对象和 DOM 对象上面
- 意外的全局变量(比如
let a = b = 1中的 b) - 大量事件监听,没有及时卸载
- 被遗忘的定时器,没有及时清除
- 闭包使用不当
-
检查办法
- 【粗略】利用浏览器 F12 的 Performance 面板
- 在 Memory 视图 中可以看到 Js 堆、Document、DOM、事件监听等内存的变化趋势,判断内存泄漏的地方
- 【精准】利用浏览器 F12 的 Memory 面板
- 专门针对堆内存,进行不同时间点的快照拍摄(Take snapshot),通过对比两次堆快照发现泄漏点
- 【粗略】利用浏览器 F12 的 Performance 面板