~/static/img/default-avatar-0.jpeg

jielu

前端开发工程师

博客

借助 Microsoft Edge 分离的元素面板分析内存泄漏问题

jielu 2022-11-14

Microsoft Edge 分离元素面板 工具简介

这是基于 chromium 内核的 Edge 浏览器,为开发者提供的一个用来分析网页 DOM 内存泄漏的工具,目前 Chrome 上还没有类似的工具。

这个面板的界面如下,左上方四个按钮功能分别为 获取分离的DOM,手动执行GC,生成内存分析以及销毁分离树内的父子链接。

具体的功能可以查看官方文档 https://learn.microsoft.com/zh-cn/microsoft-edge/devtools-guide-chromium/memory-problems/dom-leaks

d

原理,优势与局限

一个 DOM 节点只有在没有被引用时才会被 GC,当 DOM 节点不在 DOM 树中,但是却被 JS 代码保留着引用,这种情况下会造成内存泄漏。

我们可以把分离的 DOM 当作一个标志,通过分离的 DOM 节点,定位到具体导致了内存泄漏发生的 JS 代码。

对于 Vue 项目而言,因为 Vue 实例保存着 DOM 的引用,定位到 DOM 的内存泄漏,也就定位到了 Vue 实例级别的内存泄漏,通常这种级别的内存泄漏影响是会是比较大的。

在 Chrome 提供的内存分析套件中,对于 DOM 内存泄漏问题,我们可以通过记录前后内存快照比对的方式去分析。但快照分析结果并不直观,同时记录快照并分析的整个流程也很繁琐,不便于排查阶段的快速调试。

image.png

相比之下,Edge 的这个面板就很直观,首先通过分离的 DOM 的类名可以快速定位到有问题的 Vue 组件,其次获取分离的 DOM 的操作很快,我们可以很方便的使用注释法去定位问题。

该工具也有局限,就是只能用来定位与 DOM 相关的内存泄漏问题,如果是纯 JS 代造成的泄漏,还是需要使用传统快照的方式分析。

🌰 一个很坑的内存泄漏问题分析记录

下面这个例子展示如何使用该工具定位内存泄漏问题

发现问题

在测试环境中使用开发者工具中的性能监视器发现,每次执行一组操作:手动GC,打开 OMS 的新建单据页面,返回 OMS 单据列表页面,再次手动GC 后,JS 堆内存大小与DOM节点数量均稳定增加,在生产环境测试发现同样有该问题,确认为内存泄漏问题。 d

定位文件

打开分离元素工具,在执行一组操作后,获取分离的元素,发现整个表单页的 DOM 都是分离的,尝试根据内存快照去定位问题,发现引用路径都是 webpack 或 vue 内部的文件,对于定位问题没有什么帮助。

d

d

这样只能用比较笨的方法去定位了,以表单页 DOM为标志,先定位到问题文件,将全部代码注释,之后再逐个将代码块取消注释,执行操作看表单页 DOM 是否还会分离。第一步定位到是 FormFrame 组件引用的 FullScreenContainer 组件,接着进一步定位到是 FullScreenContainer 组件中动态的 style 标签引起的问题。

d

d

尝试解决

动态的 style 标签会导致整个组件内存泄漏,这个问题十分奇怪,不过粗暴的解决办法是有的 — 改变一下代码写法,使用操作 DOM 的方式动态添加 style。

d

改动后,执行一组操作,发现表单页的 DOM 不在是分离的了,但是又出现了新的奇怪问题,但是本应通过 document.body.removeChild style 标签,却成为了分离的元素。

d

发现最终原因

这时使用内存分析工具,发现 style 元素引用路径中的 assets-retry.umd.js 非常可疑,指向了近期添加的资源重试机制而引入的依赖。

d

分析该依赖包的源码发现,该包使用定时器查询样式表,不断将 style元素加入缓存,但是当 style 元素从 dom 中移除时,该包并没有其从缓存中移除,导致 style 元素无法被 GC。

d

整个分析过程中,分离元素工具发挥了比较关键的作用

作者相关知识精选
  • webpack5 资源模块

    webpack5新增了资源模块(asset module),允许使用资源文件(字体,图标等)而无需配置额外 loader。资源模块定义了四种类型,用以代替之前的 file-loaderurl-loaderraw-loader

    • asset/resource 代替 file-loader 实现。
    • asset/inline 代替 url-loader 实现。
    • asset/source 代替 raw-loader 实现。
    • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

    原先 loader 中的选项配置可以使用资源模块相应的配置项代替

    // before
    {
      test: /\.url\.(png|jpe?g|gif|webp)(\?.*)?$/,
      use: [
        /* config.module.rule('images').use('url-loader') */
        {
          loader: 'url-loader',
          options: {
            limit: 4096,
            fallback: {
              loader: 'file-loader',
              options: {
                name: 'static/img/[name].[contenthash:8].[ext]',
    						publicPath: 'https://cfile.xiaoman.cn/${CDN_ENV}/'
              }
            }
          }
        }
      ]
    }
    // after
    {
      test: /\.url\.(png|jpe?g|gif|webp)(\?.*)?$/,   
      type: 'asset',
      generator: {
        filename: 'static/img/[name].[hash:8][ext]'
        publicPath: 'https://cfile.xiaoman.cn/${CDN_ENV}/'
      },
      parser: {
    	  dataUrlCondition: {
    	    maxSize: 4096
    	  }
      }
    }
    

    官方文档: https://webpack.docschina.org/guides/asset-modules/

    查看更多
  • 零宽字符

    零宽字符是一系列不显示内容也不占用宽度的不可打印的字符

    如 零宽空格(zero-width space, ZWSP)Unicode: \u200B Urlencode: %E2%80%8B

    若前端发送的请求参数重包含了零宽空格,虽然看起来一切正常,后端会因为数据库中无法查询到数据导致无法返回预期的结果

    查看更多
  • 复制 git 暂存区中的文件到临时目录的脚本

    可用于辅助处理技术栈升级代码同步的工作

    mkdir temp
    // 复制被修改的文件
    git status | grep modified | awk '{print $2}' |  xargs -I % dirname % > temp.txt
    cat temp.txt | xargs -I % mkdir -p ./temp/%
    git status | grep modified | awk '{print $2}' |  xargs -I % cp % ./temp/%
    // 复制新增的文件
    git status | grep 'new file' | awk '{print $3}' |  xargs -I % dirname % > temp.txt
    cat temp.txt | xargs -I % mkdir -p ./temp/%
    git status | grep 'new file' | awk '{print $3}' |  xargs -I % cp % ./temp/%
    
    查看更多