Skip to main content

GC

GCGarbage 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 算法,分为 fromto 两个空间:

工作流程:

  1. 遍历对象,标记活动对象和非活动对象
  2. 复制 from 的活动对象到 to 中
  3. 释放 from 中的非活动对象的内存
  4. 将 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 循环中定义对象(包括函数)