阅读 560

JS 中的内存回收和内存泄露

总结

  1. js 的垃圾回收是周期性的,根据判断变量是否在被继续使用,如果不被使用则作为垃圾回收
  2. 常用的策略是引用计数标记清除。引用计数判断变量是否有指向它的引用,没有的话回收,这种方式有循环引用的问题;标记清除判断变量是否可达,不可达则回收。
  3. 内存泄露可以通过 chrome 开发者工具进行快照 snapshot 或者 memory record,也可以通过这种方式进行问题排查
  4. 内存泄露可能原因:意外的全局变量闭包未释放定时器操作dom 操作

垃圾回收

JS 的自动垃圾回收机制:引用计数和标记清除,原理是找出那些不再继续使用的值,然后释放其占用的内存。全局变量的生命周期直至浏览器卸载页面才会结束,被垃圾回收的是局部变量。

引用计数

内存不再使用,看一个对象是否有指向它的引用,如果没有其他对象指向它了,说明该对象已经不再需要了。

问题是:循环引用

  • 如果两个对象相互引用,尽管已经不再使用,垃圾不会回收,导致内存泄露。
var div = document.createElement("div");
div.onclick = function() {
    console.log("click");
};
复制代码

变量 div 有事件处理函数的引用,事件处理函数有 div 的引用(可以在函数内访问)。

标记清除(各大浏览器常用)

将不再使用的对象定义为无法达到的对象,就是从根部(JS 中是全局对象)触发定时扫描内存中的对象,凡是从根部能访问到的,都是还需要使用的,无法触及的,就标记为垃圾清除。

工作流程

  1. 垃圾收集器会在运行的时候会给存储在内存中的所有变量都加上标记。
  2. 从根部出发将能触及到的对象的标记清除。
  3. 那些还存在标记的变量被视为准备删除的变量。
  4. 最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间。

GC 缺陷

GC 时,停止响应其他操作,这是为了安全考虑。

  • 优化:避免 GC 造成的长时间停止响应。
  • 优化策略:分代回收。

分代回收

通过区分临时与持久对象:多回收临时对象区,少回收持久对象区,减少每次需遍历的对象,从而减少每次 GC 的耗时。

内存泄露

由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。

识别方法

经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄露。

查看内容构成

在 console 中:

class Jane {
}

class Tom {
  constructor () {  this.jane = new Jane()
  }
}

Array(1000000).fill('').map(() => new Tom())
复制代码

Memory 标签下,heap snapshot,获取成功后有一个表格:

snapshot

  • Constructor:对象的类名;
  • Distance:对象到根的引用层级;
  • Objects Count:对象的数量;
  • Shallow Size: 对象本身占用的内存,不包括引用的对象所占内存;
  • Retained Size: 对象所占总内存,包含引用的其他对象所占内存;
  • Retainers:对象的引用层级关系。
  1. Tom 中,除对象本身占用内存,还包括引用 Jane 的内存,所以 Shallow SizeRetain Size 小,刚好小了 Jane 对象本身占用内存大小。
  2. retained size 可以理解为当回收掉该对象时可以释放的内存大小,在内存调优中具有重要参考意义。

查看内存是否泄露

如果你的网页在放久了的情况下内存越来越大甚至 tab 页崩溃,那就要考虑是否内存泄露了。

  • 通过 Chrome 的任务管理器可以看到 JavaScript 所占用的内存。
  • 通过 Performance 里的 record 也可以直观地看到内存的增长(需要勾上 Memory 选项)。
  • 通过 snapshot 对比,查看新的 snapshot 增加的部分。

snapshot1

服务器

Node 提供的 process.memoryUsage 方法。

该方法返回一个对象,包含 Node 进程的内存占用信息,包含四个字段,内存看 heapUsed 字段。

常见的内存泄露案例

意外的全局变量

定义变量时没有用 var,或者用 this

被遗忘的定时器和回调函数

定时器回收,回调函数回收

闭包

内部函数有权访问包含其的外部函数中的变量

DOM 引用

对 dom 的操作,注意表格元素 td,在表格删除之后该引用还是会保留对父元素的引用,导致整个表格无法进行内存回收。

避免内存泄露

  • 减少不必要的全局变量,使用严格模式避免意外创建全局变量。
  • 在使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
  • 组织好的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。

es6 的 weakSet 和 weakMap

weakMap 里的引用是弱引用,不会被计入垃圾回收机制。只要引用清除,weakmap 内部的引用就会被垃圾回收清除。

文章分类
前端
文章标签