前端vue项目内存泄漏排查总结
名词
内存泄漏(Memory Leak):不再用到的内存,没有及时释放;
内存溢出(Out Of Memory):应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。
案例背景
- 项目由
vue-element-admin改造,二次封装了element-ui,组件套的有点深; - 系统上线一段时间后,业务频繁反馈页面崩溃;
- 项目使用了
keep-alive页面缓存,移除的话没问题,但因业务需要不能移除:

内存泄漏排查方法 - 谷歌开发者工具 Chrome Devtools
1. performance打快照;
a. 在面板勾上
内存,点击垃圾桶手动进行一次垃圾回收,避免干扰,然后点击第一个灰色实心圆圈图标开始录制;
b. 操作页面;
c. 再点击垃圾桶手动进行一次垃圾回收,然后点击停止录制按钮;

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

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


4. memory时间轴查看未释放内存;
a. 点击垃圾桶手动进行一次垃圾回收,再点击圆圈进行录制;
b. 操作页面;
c. 再点击垃圾桶手动进行一次垃圾回收,然后点击圆圈停止录制;
排查
1. 使用谷歌开发工具面板performance、memory排查,打快照,发现详情页内存一直累积,无法释放;
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)
}
解决方案
- 缓存的页面走
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. 比较粗暴的方法:主动销毁组件,详情页离开(销毁)之前,递归把组件的componentInstance、elm.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. 把vue、vue-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.13在mounted、updated都执行了this.cacheVNode()。
这里this.vnodeToCache = null,把vnode的引用断开了,而旧版本的cache[key] = vnode是没有做这个操作。
这里的缓存只用到三个参数:组件的name、tag、实例componentInstance,如果一直保持引用,会导致缓存无法释放。
keep-alive是一把双刃剑,使用好能提高用户体验,没使用好效果却相反。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)