阅读 143

vue2中keepalive手动清理内存,存在子路由内存无法回收的问题

起因

近期客户经常反馈系统崩溃的问题,尤其是在下午最频繁,经过自己的自测,发现系统tab关闭后内存并没有回收掉,目前我已经处理了,tab页签关闭后,手动清理keep-alive内的缓存,应该不存在内存泄漏的情况,看来还有其他地方的缓存没有清理掉。

定位问题

1.还原场景

公司项目是单页应用,所有的操作都在一个浏览器页签内操作,整个页面是通过Layou+子路由的方式布局的,路由层级达到4级,业务复杂繁琐。需要重新搭建一个纯净项目还原场景

2.写个demo

使用vue-cli创建项目,vue@2.7.9vue-router@3.6.5

main.js

import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({   render: h => h(App),   router }).$mount('#app') 复制代码

App.vue

<template>   <router-view></router-view> </template> <script> export default {   name: "App", }; </script> 复制代码

view/Page1、view/Page2、view/A1、view/A2

<template>   <div>     Page1     <router-view></router-view>   </div> </template> <script> export default {   name: "Page1", }; </script> <template>   <div>     Page2     <router-view></router-view>   </div> </template> <script> export default {   name: "Page2", }; </script> <template>   <div>组件view A1</div> </template> <script> export default {   name: "A1",   data() {     return {       a: new Array(20000000).fill(1), //大概80mb     };   }, }; </script> <template>   <div>组件view A2</div> </template> <script> export default {   name: "A2",   data() {     return {       a: new Array(20000000).fill(1), //大概80mb     };   }, }; </script> 复制代码

view/Layout.vue

<template>   <div>     <h1>Layout</h1>     <div class="box">       <p>二级路由</p>       <router-link :to="{ name: 'A' }">A</router-link>       <br />       <router-link :to="{ name: 'B' }">B</router-link>     </div>     <div class="box">       <p>三级路由</p>       <router-link :to="{ name: 'AA' }">Page1</router-link>       <br />       <router-link :to="{ name: 'BB' }">Page2</router-link>     </div>     <div class="box">       <button @click="includeRemove()">清理keepalive缓存</button>       <br />       <router-link to="/home">Home</router-link>       <br />     </div>     <h1>keep-alive</h1>     缓存页面:{{ include }}     <keep-alive :include="include">       <router-view ref="alive"></router-view>     </keep-alive>   </div> </template> <script> export default {   name: "Layout",   data() {     return {       include: [],     };   },   watch: {     '$route'(val) {       const name = val.meta.name       if (name && !this.include.includes(name)) {         this.include.push(name);       }     }   },   methods: {     includeRemove() {       this.include = [];     },   },   mounted() {}, }; </script> <style> .box {   margin-bottom: 20px; } </style> 复制代码

router/index.js

import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) const router = new Router({   mode: "hash",   routes: [     {       path: "/",       redirect: "/home",       component: () => import("../view/Layout.vue"),       children: [         {           path: "home",           component: () => import("../view/Home.vue"),         },       ],     },     {       path: "/a",       component: () => import("../view/Layout.vue"),       children: [         {           path: "a",           name: "A",           meta: {             name: 'A1'           },           component: () => import("../view/A1.vue"),         },       ],     },     {       path: "/b",       component: () => import("../view/Layout.vue"),       children: [         {           path: "b",           name: "B",           meta: {             name: 'A2'           },           component: () => import("../view/A2.vue"),         },       ],     },     {       path: "/a",       component: () => import("../view/Layout.vue"),       children: [         {           path: "page1",           name: "Page1",           component: () => import("../view/Page1.vue"),           children: [             {               path: "a",               name: "AA",               meta: {                 name: 'Page1'               },               component: () => import("../view/A1.vue"),             },           ],         },       ],     },     {       path: "/b",       component: () => import("../view/Layout.vue"),       children: [         {           path: "page2",           name: "Page2",           component: () => import("../view/Page2.vue"),           children: [             {               path: "b",               name: "BB",               meta: {                 name: 'Page2'               },               component: () => import("../view/A2.vue"),             },           ],         },       ],     },   ], }); export default  router 复制代码

运行效果

image.png

3.重现问题

可以看出,初始情况下,内存使用7.7MB左右

1.点击A、B后,内存占用168MB

image.png

2.点击Home,保证在清理缓存时,路由不占用A,B组件,再次点击清理keepalive缓存

image.png

手动GC后,发现内存使用变为7.9MB,说明A、B组件成功释放掉了,这个模拟了公司项目前期只有二级路由的情况,那个时候还不存在系统崩溃的问题,这里也刚好印证了。

3.点击Page1、Page2,内存占用是168MB

image.png

4.点击Home,再次点击清理keepalive缓存

image.png

这个时候就出问题了,内存并没有成功的释放掉,问题找到了

4.分析

首先记录初始情况下内存占用

image.png

打开Page1、Page2,切换到Home页面,清理keepalive缓存,记录当前内存快照

image.png

从图中可以看出,A1组件还存在,并且是vue-router引用了,nameMap保存了所有的路由信息,这样的话问题就找到了 image.png

初始状态下路由信息

image.png

打开Page1、Page2,切换到Home页面,清理keepalive缓存后路由信息

image.png 只有Page1、Page2的instances.default是undefined,A1,B1还保留了组件实例

测试另外一种情况,清理keepalive缓存前不切换到Home,这种情况下,内存是可以成功释放掉的。

5.结论

  1. 如果是在当前路由关闭tab,然后清理keepalive缓存,内存是可以正常回收的

  2. 如果是在其他路由关闭非激活的路由时,二级路由组件可以正常回收,二级以下路由内存回收异常,猜测非激活路由matched内的信息以变更了,毕竟是单例模式,这就说的通,为啥激活的路由移除缓存是正常的了

3.修复问题

1.思路

  1. 获取关闭当前tab路由父子关系

  2. 通过所有的路由信息,遍历删除相关路由的instances.default

2.具体代码实现

includeRemove() {   this.include = [];   // 为啥vue-router不开放直接获取nameMap的接口 淦   const routes = this.$router.getRoutes()   const nameMap = new Map()   for (let index = 0; index < routes.length; index++) {     const r = routes[index];     nameMap.set(r.name, r)   }   // 假设我这边获取到了当前移除的tab页签,具体代码根据具体项目实现   const rList = ['AA', 'BB']   for (let index = 0; index < rList.length; index++) {     const name = rList[index];     const r = nameMap.get(name)     if (r) r.instances.default = undefined   } } 复制代码

代码改造后重新按照流程走了一遍,内存使用情况如下,成功解决问题

image.png

总结

一开始根本无从下手,总觉得是客户操作问题,或者是电脑内存太小,一直没有太在意,当更多人反馈这个问题时,才意识到这个问题很普遍,必须得解决掉才行,从开始解决到已解决花了一个星期的时间,解决的代码很简单,但是解决问题的过程很艰辛。


作者:释迦摩尼
链接:https://juejin.cn/post/7169778048374898719


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐