名词

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

案例背景

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

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

1. performance打快照;

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

 

2. performance monitor性能监控器实时查看变化,同样要记得进行垃圾回收;

 

3. memory打堆快照,页面操作前拍摄一次,操作拍摄一次,然后筛选快照之间分配的对象;

4. memory时间轴查看未释放内存;

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

 

排查

1. 使用谷歌开发工具面板performancememory排查,打快照,发现详情页内存一直累积,无法释放;

2. 列表页和详情页引用了公共组件(注意import引入的资源在内存中实质上是同一个对象);

3. memory排查分析,发现累积的都是些vue组件;

4.  注意组件的 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移到外面,即:
  <transition name="fade-transform" mode="out-in">
    <div>
      <keep-alive :include="cachedViews" :max="10">
        <!-- 匹配三级路由,并缓存 -->
        <router-view v-if="!$route.meta.noCache" :key="key" />
      </keep-alive>
      <router-view v-if="$route.meta.noCache" :key="key" />
    </div>
  </transition>

2. 比较粗暴的方法:主动销毁组件,详情页离开(销毁)之前,递归把组件的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)
  }
}

3. 把vuevue-template-compiler都升级到2.6.13版本;

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

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

keep-alive组件部分源码:

vue@2.6.10

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

vue@2.6.13新增的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;
  }

vue@2.6.13mountedupdated都执行了this.cacheVNode()

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

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

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

GitHub 加速计划 / vu / vue
82
16
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:4 个月前 )
9e887079 [skip ci] 3 个月前
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 6 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐