GC
GC
即 Garbage Collection
,代码执行过程中会产生很多垃圾(即无用的内存),js 引擎自带 GC
,会自动处理这些垃圾并释放内存
举个例子
let obj = {
name: "JacksonZhou",
};
obj = ["JacksonZhou"];
js 引用类型数据是保存在堆内存中的,因此 obj
实际上是{name: "JacksonZhou"}
在堆内存中的引用地址,当它重新赋值后 obj = ['JacksonZhou']
,新建了一个引用类型数据,并更新 obj
为新的引用地址,原先的 {name: "JacksonZhou"}
还存在,但是已经没有变量引用了,成为了无用的对象,并占据着一定的内存
垃圾回收的目的是 周期性地寻找那些不再使用的变量,并释放其所占用的内存,防止出现内存泄漏
v8 被限制了内存的使用(64 位约 1.4G/1464MB , 32 位约 0.7G/732MB)
分代回收
V8 GC 采用 分代回收 的策略,分代指的是新生代和老生代
新生代回收
新生代采用 Scavenge
算法,分为 from
和 to
两个空间:
工作流程:
- 遍历对象,标记活动对象和非活动对象
- 复制 from 的活动对象到 to 中
- 释放 from 中的非活动对象的内存
- 将 from 和 to 角色互换
符合条件的新生代将晋升为老生代:
- 已经经历过一次 Scavenge 回收
- To 空间已经超过 25% 被使用了
老生代回收
采用 mark-sweep
标记清除和 mark-compact
标记整理。通常存放较大的内存块和从新生代分配过来的内存块
标记清除:
- 第一次遍历标记活动对象
- 第二次遍历清除未被标记的对象(非活动对象)
标记整理:会将活动的对象往堆内存的一端进行移动,移动完成后再清理掉边界外的全部内存
频繁触发垃圾回收会影响引擎的性能,内存空间不足时也会优先触发 Mark-compact
引用计数
ie9 之前使用的方法,现在基本已经被弃用了
- 当一个变量引用一个对象时,该对象的引用次数+1;
- 如果同一个对象又被另一个变量引用,则引用次数+1;
- 如果引用该对象的变量又取得了另外一个值,则该值的引用次数-1
// count({}) = 0
let obj = {}; // count({})+1=1
let obj2 = obj; // count({})+1=2
obj2 === null; // count({})-1=1
缺点:循环引用
内存泄露
内存泄漏是指内存空间明明已经不再被使用,但由于某种原因并没有被释放的现象
主要有以下几种方式容易造成内存泄漏:
1. 闭包
2. 意外的全局变量
3. 被遗忘的定时器或者回调函数
4. 没有清理的DOM元素引起的内存泄漏
let element = document.getElementById("element")
element.mark = "marked"
// 移除 element 节点
function remove() {
element.parentNode.removeChild(element)
}
上面的代码,我们只是把 id 为 element 的节点移除
但是变量 element 依然存在,该节点占有的内存无法被释放
如何检测
- 点开浏览器 devtools 的 Performance
- 勾选屏幕截图和 Memory
- 录制
堆内存会周期性地分配和释放,如果堆内存的 min 值在逐渐上升则说明存在内存泄漏
优化
- 规避以上四种内存泄漏的方式
- 尽量不在 for 循环中定义对象(包括函数)