借助 Microsoft Edge 分离的元素面板分析内存泄漏问题
https://fe.okki.com/post/63721db5e81bf53bcf1cbff7/
https://learn.microsoft.com/zh-cn/microsoft-edge/devtools-guide-chromium/memory-problems/dom-leaks
Microsoft Edge 分离元素面板 工具简介
这是基于 chromium 内核的 Edge 浏览器,为开发者提供的一个用来分析网页 DOM 内存泄漏的工具,目前 Chrome 上还没有类似的工具。
这个面板的界面如下,左上方四个按钮功能分别为 获取分离的DOM,手动执行GC,生成内存分析以及销毁分离树内的父子链接。
具体的功能可以查看官方文档 https://learn.microsoft.com/zh-cn/microsoft-edge/devtools-guide-chromium/memory-problems/dom-leaks
原理,优势与局限
一个 DOM 节点只有在没有被引用时才会被 GC,当 DOM 节点不在 DOM 树中,但是却被 JS 代码保留着引用,这种情况下会造成内存泄漏。
我们可以把分离的 DOM 当作一个标志,通过分离的 DOM 节点,定位到具体导致了内存泄漏发生的 JS 代码。
对于 Vue 项目而言,因为 Vue 实例保存着 DOM 的引用,定位到 DOM 的内存泄漏,也就定位到了 Vue 实例级别的内存泄漏,通常这种级别的内存泄漏影响是会是比较大的。
在 Chrome 提供的内存分析套件中,对于 DOM 内存泄漏问题,我们可以通过记录前后内存快照比对的方式去分析。但快照分析结果并不直观,同时记录快照并分析的整个流程也很繁琐,不便于排查阶段的快速调试。
相比之下,Edge 的这个面板就很直观,首先通过分离的 DOM 的类名可以快速定位到有问题的 Vue 组件,其次获取分离的 DOM 的操作很快,我们可以很方便的使用注释法去定位问题。
该工具也有局限,就是只能用来定位与 DOM 相关的内存泄漏问题,如果是纯 JS 代造成的泄漏,还是需要使用传统快照的方式分析。
🌰 一个很坑的内存泄漏问题分析记录
下面这个例子展示如何使用该工具定位内存泄漏问题
发现问题
在测试环境中使用开发者工具中的性能监视器发现,每次执行一组操作:手动GC,打开 OMS 的新建单据页面,返回 OMS 单据列表页面,再次手动GC 后,JS 堆内存大小与DOM节点数量均稳定增加,在生产环境测试发现同样有该问题,确认为内存泄漏问题。
定位文件
打开分离元素工具,在执行一组操作后,获取分离的元素,发现整个表单页的 DOM 都是分离的,尝试根据内存快照去定位问题,发现引用路径都是 webpack 或 vue 内部的文件,对于定位问题没有什么帮助。
这样只能用比较笨的方法去定位了,以表单页 DOM为标志,先定位到问题文件,将全部代码注释,之后再逐个将代码块取消注释,执行操作看表单页 DOM 是否还会分离。第一步定位到是 FormFrame 组件引用的 FullScreenContainer 组件,接着进一步定位到是 FullScreenContainer 组件中动态的 style 标签引起的问题。
尝试解决
动态的 style 标签会导致整个组件内存泄漏,这个问题十分奇怪,不过粗暴的解决办法是有的 — 改变一下代码写法,使用操作 DOM 的方式动态添加 style。
改动后,执行一组操作,发现表单页的 DOM 不在是分离的了,但是又出现了新的奇怪问题,但是本应通过 document.body.removeChild style 标签,却成为了分离的元素。
发现最终原因
这时使用内存分析工具,发现 style 元素引用路径中的 assets-retry.umd.js 非常可疑,指向了近期添加的资源重试机制而引入的依赖。
分析该依赖包的源码发现,该包使用定时器查询样式表,不断将 style元素加入缓存,但是当 style 元素从 dom 中移除时,该包并没有其从缓存中移除,导致 style 元素无法被 GC。
整个分析过程中,分离元素工具发挥了比较关键的作用