前言
引起内存泄露的方法有很多,但最终都是因为这些变量可以从根节点被访问,本文以此为切入口,通过几个🌰帮助大家定位到引发内存泄露的具体位置
内存泄露理论基础
浏览器内存回收机制
现在浏览器普遍采用的是标记清除法(实际的运行过程会跟复杂这里简化下,你只需要知道下面信息即可)
- 第一阶段:标记。从根结点出发遍历对象,对访问过的对象打上标记,表示该对象可达。
- 第二阶段:清除。对那些没有标记的对象进行回收,这样使得不能利用的空间能够重新被利用。
由此可知,之所以会内存泄露,是因为标记阶段总是能访问到该变量(不管是什么语法引起的)
使用的chrome工具
- 看图大概知道个意思,后续会介绍
demo案例
1.准备demo
- 随便创建一个项目,笔者用的是
vue-cli
- 然后简单处理下,本例是模拟的是组件卸载后内存泄露问题,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.操作步骤
- 启动后进入home页面,点一次强制GC,会看到初始化有53个dom节点
- 路由切换about再切回home,点强制GC,观察dom数量为65。说明about组件卸载了(可以从控制台看到已经打印destroyed了)但内存中依然保留着它的数据
- 之后每次执行第二步操作,都会稳定多11个dom
3.查看内存
- 刷新页面,重新执行操作步骤中的1-2两步,点一次强制GC后开始录制内存快照
- 点击快照查看内容,默认是以构造函数归类内存中所有对象
- 搜索
detached,即可查看已经脱离document的dom元素 - 点击可以看到这个分离的dom的调用链,就知道为啥GC不了了。可以看到,dom是被vue组件实例的$el引用,接着是this、箭头函数、普通函数、ScheduledAction、DOMTimer(这两个是计时器相关的对象)然后是挂在了window上的内部节点里,当然window也是挂在一个v8私有属性上的。如果是用户代码创建的对象,就可以定位到具体的代码
- 根据GC规则,凡是从系统根节点可以访问到的对象就不可以回收。所以根据这个规则,只要合理查找相关构造函数,就能找到对应的问题代码。这边遇到的严重的内存泄露往往伴随着分离的dom,所以都是一招搞定,其他的还可以通过录制异常内容前后的快照,查看增量内存、搜索关键字符串等方式排查
另一个真实案例
课后复习,vue2的函数式组件开发环境会引起内存泄露,有兴趣的可以尝试练习下
包工头喊我搬砖了,搬完更新...