使用Chrome排查前端内存泄露问题

7,871 阅读2分钟

前言

引起内存泄露的方法有很多,但最终都是因为这些变量可以从根节点被访问,本文以此为切入口,通过几个🌰帮助大家定位到引发内存泄露的具体位置

内存泄露理论基础

浏览器内存回收机制

现在浏览器普遍采用的是标记清除法(实际的运行过程会跟复杂这里简化下,你只需要知道下面信息即可)

  • 第一阶段:标记。从根结点出发遍历对象,对访问过的对象打上标记,表示该对象可达。
  • 第二阶段:清除。对那些没有标记的对象进行回收,这样使得不能利用的空间能够重新被利用。

由此可知,之所以会内存泄露,是因为标记阶段总是能访问到该变量(不管是什么语法引起的)

使用的chrome工具

  • 看图大概知道个意思,后续会介绍 image.png

demo案例

1.准备demo

  • 随便创建一个项目,笔者用的是vue-cli

image.png

  • 然后简单处理下,本例是模拟的是组件卸载后内存泄露问题,Home组件跟About组件之间切换,About组件内存泄露
  • Home组件
<template>
  <div>home</div>
</template>
  • About组件
<template>
  <div>
    <div>about</div>
    <div>about</div>
    <div>about</div>
    <div>about</div>
    <div>about</div>
  </div>
</template>
<script>
export default {
  created() {
    setInterval(() => {
      this.$data.test += 1
    }, 100)
  },
  destroyed() {
    console.log('About destroyed')
  },
}
</script>

2.操作步骤

  1. 启动后进入home页面,点一次强制GC,会看到初始化有53个dom节点 image.png
  2. 路由切换about再切回home,点强制GC,观察dom数量为65。说明about组件卸载了(可以从控制台看到已经打印destroyed了)但内存中依然保留着它的数据 image.png
  3. 之后每次执行第二步操作,都会稳定多11个dom

3.查看内存

  1. 刷新页面,重新执行操作步骤中的1-2两步,点一次强制GC后开始录制内存快照 image.png
  2. 点击快照查看内容,默认是以构造函数归类内存中所有对象 image.png
  3. 搜索detached,即可查看已经脱离document的dom元素 image.png
  4. 点击可以看到这个分离的dom的调用链,就知道为啥GC不了了。可以看到,dom是被vue组件实例的$el引用,接着是this、箭头函数、普通函数、ScheduledAction、DOMTimer(这两个是计时器相关的对象)然后是挂在了window上的内部节点里,当然window也是挂在一个v8私有属性上的。如果是用户代码创建的对象,就可以定位到具体的代码 image.png
  5. 根据GC规则,凡是从系统根节点可以访问到的对象就不可以回收。所以根据这个规则,只要合理查找相关构造函数,就能找到对应的问题代码。这边遇到的严重的内存泄露往往伴随着分离的dom,所以都是一招搞定,其他的还可以通过录制异常内容前后的快照,查看增量内存、搜索关键字符串等方式排查

另一个真实案例

课后复习,vue2的函数式组件开发环境会引起内存泄露,有兴趣的可以尝试练习下

包工头喊我搬砖了,搬完更新...