前端vue项目内存泄漏排查总结

名词

内存泄漏(Memory Leak):不再用到的内存,没有及时释放;
内存溢出(Out Of Memory):应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。

案例背景

  1. 项目由vue-element-admin改造,二次封装了element-ui,组件套的有点深;
  2. 系统上线一段时间后,业务频繁反馈页面崩溃;
  3. 项目使用了keep-alive页面缓存,移除的话没问题,但因业务需要不能移除:
内存溢出.png

内存泄漏排查方法 - 谷歌开发者工具 Chrome Devtools

  1. performance打快照;

a. 在面板勾上内存,点击垃圾桶手动进行一次垃圾回收,避免干扰,然后点击第一个灰色实心圆圈图标开始录制;
b. 操作页面;
c. 再点击垃圾桶手动进行一次垃圾回收,然后点击停止录制按钮;

内存泄漏排查 - performance.png
  1. performance monitor性能监控器实时查看变化,同样要记得进行垃圾回收;
内存泄漏排查 - performance-monitor 性能监控器.gif
  1. memory打堆快照,页面操作前拍摄一次,操作拍摄一次,然后筛选快照之间分配的对象;
内存泄漏排查 - memory 打堆快照.gif

操作前后快照对比 - vue组件.png
  1. memory时间轴查看未释放内存;

a. 点击垃圾桶手动进行一次垃圾回收,再点击圆圈进行录制;
b. 操作页面;
c. 再点击垃圾桶手动进行一次垃圾回收,然后点击圆圈停止录制;

内存泄漏排查 - memory 时间轴查看未释放部分.gif

排查

  1. 使用谷歌开发工具面板performancememory排查,打快照,发现详情页内存一直累积,无法释放;
  2. 列表页和详情页引用了公共组件(注意import引入的资源在内存中实质上是同一个对象);
  3. memory排查分析,发现累积的都是些vue组件;
vueComponent.png
操作前后快照对比 - vue组件的name.jpg
分离的dom没有回收.png
  1. 注意组件的name与路由的name保持一致且唯一,不然用keep-alive无法正常缓存(通过componentOptions.Ctor.options.name || componentOptions.tag决定include包含的组件缓存);
function getComponentName (opts) {
  return opts && (opts.Ctor.options.name || opts.tag)
}

解决方案

  1. 缓存的页面走keep-alive,不缓存的把router-view移到外面,即:
  
    
  1. 比较粗暴的方法:主动销毁组件,详情页离开(销毁)之前,递归把组件的componentInstanceelm.innerHTML移除,即把组件的联系断开,以便浏览器回收(GC):
/**
 * @name 深度销毁组件
 * @description 用于没有使用 keep-alive 缓存的页面(详情页)
 */

export default {
  beforeDestroy() {
    async function destroyDeep(vnode) {
      let vnodes
      if (vnode.children || vnode.componentInstance?._vnode?.children) {
        vnodes = vnode.children || vnode.componentInstance._vnode.children
        for (const vn of vnodes) {
          destroyDeep(vn)
        }
      }

      vnode.componentInstance?.$destroy()
      setTimeout(() => {
        vnode.componentInstance = undefined
        vnode.elm.innerHTML = ''
      }, 0)
    }

    destroyDeep(this._vnode)
  }
}
  1. vuevue-template-compiler都升级到2.6.13版本;

但有个情况依然有问题,就是两个不同列表(缓存)的详情页(不缓存)之间切换,详情页引用同一个详情组件的时候内存一样不释放。

issues keep-alive内存溢出
issues 源码修改commit
issues 源码改动部分

keep-alive组件部分源码:

[email protected]

  if (cache[key]) {
    vnode.componentInstance = cache[key].componentInstance;
    ...
  } else {
    cache[key] = vnode;
    ...
  }

[email protected]新增的cacheVNode方法

  if (vnodeToCache) {
    var tag = vnodeToCache.tag;
    var componentInstance = vnodeToCache.componentInstance;
    var componentOptions = vnodeToCache.componentOptions;
    cache[keyToCache] = {
      name: getComponentName(componentOptions),
      tag: tag,
      componentInstance: componentInstance,
    };
    ...
    this.vnodeToCache = null;
  }

[email protected]mountedupdated都执行了this.cacheVNode()

这里this.vnodeToCache = null,把vnode的引用断开了,而旧版本的cache[key] = vnode是没有做这个操作。

这里的缓存只用到三个参数:组件的nametag、实例componentInstance,如果一直保持引用,会导致缓存无法释放。

keep-alive是一把双刃剑,使用好能提高用户体验,没使用好效果却相反。

你可能感兴趣的:(前端vue项目内存泄漏排查总结)