美文网首页程序员
前端内存泄露分析

前端内存泄露分析

作者: oNexiaoyao | 来源:发表于2022-04-19 16:42 被阅读0次

背景

  • 公司的一个客户端系统,采用的技术栈是:Electron + Vue + Rpc

  • 该系统属于实时监控系统,其中最主要的两大功能是:

    • 组态图展示

      有很多的svg图需要展示,图上既有实时数据也有历史数据,取数方式采用定时器轮询请求的方式

    • 实时告警

      告警信息需要以全局弹窗+表格的方式显示,并且需要时效性。使用的是 elementUiel-dialog 组件。取数方式采用kafka推送 + 接口请求的方式实现

  • 该项目的编码是由外包人员完全开发完成,公司内部人员并未参与,后期的项目维护由公司同事负责。

现象

  • 客户端开发完成后交付现场部署使用,已经部署过几个现场。据反馈,在实际使用过程中,存在页面卡顿问题,但关掉客户端重启就会好点,并不会影响业主的使用,因而没有得到过多的关注。

  • 最新部署的现场反馈,客户端不能正常使用,客户端放置一夜后第二天会出现卡顿卡死现象,只有重启系统才能恢复。

分析

  • 和其他现场对比客户端环境,发现该现场内存过小,因此提议先增加内存观察。内存扩大一倍,发现系统仍然会卡死,只是到达卡死的时间会长点。推测存在内存泄露问题。

  • 现场反馈当有实时告警的时候,系统卡死的现象会好点。对比其他现场发现,该现场实时告警多,而且频率很高。通过任务管理器发现,当告警弹窗不断更新的时候,系统的 cpu 飙升的很快,而且很高。仔细查看代码发现实时告警弹窗的显示与否使用的是 v-if 指令。而且告警的表格数据是持续累加的,意思就是如果客户端如果一直不关闭,那么告警的表格数据会一直增大。另外,查看控制台,发现控制台动态打印很多 console.log, 而且这个日志输出是和请求相关的,打印了很多数据信息。

    v-if指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。当切换v-if模块时,Vue.js 有一个局部编译/卸载过程,因为 v-if 之中的模板也可能包括数据绑定或子组件。v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

    简单的理解就是,v-if 指令是动态的创建 dom 和销毁 dom, 而对 dom 的操作是非常消耗性能的。官网建议只有在运行时条件很少改变的情况下才使用 v-if。而我们的系统由于是实时告警进行弹窗显示,需要非常频繁的更新弹窗,不适合使用该指令 v-if 来不断的创建弹窗然后又销毁。

  • 根据上面的分析对相应代码进行了修改,原以为问题得到解决了。。结果发现虽然页面没有很快卡死,但是内存占用在逐步上升。客户端放在现场不进行任何操作,只是不断地有实施告警弹窗弹出,放置一夜后发现内存的占比竟然增加了好多。而且发现一旦进行页面操作,内存会持续上升不会释放,终于在我不断的页面操作下,系统在又放置了一夜后,终于因内存溢出而卡死了。。原本以为可以很愉快的度过一个周末的,结果现在整个人都不好了。。这下真的是内存泄露的。。芭比Q了。。

  • 怎么办,只能继续深入研究。。。

  • 由于 electron 内置的是 chrome 的浏览器内核,我们可以直接在系统中调用谷歌浏览器的开发者工具中 performance 来监控。

    • 单一页面监控


      memory3-1.png
    • 页面切换监控


      memory4-1.png
  通过上面的监控效果来看,很明显的存在内存泄露。  
  • 第一步从网上查阅有关 Vue内存泄露信息, 按照网上查阅到的几种可能导致内存泄露的方式检查了下代码,感觉没有什么实质性的收获(这个过程顺带把系统中一些无用的代码该优化的的优化,该注释的注释了)。

  • 第二步添加扩展插件来发现。由于刚开始我的怀疑重点还是放在 Vue 上,因此我首先安装了 vue-devtool,检查 vue 的组件性能,结果发现好像发现不了什么。

    memory5.png
  • 继续往下研究。。。不断的换思路,不断的换查看方式。。最终发现了下面的问题。。

  • 使用谷歌浏览器中的开发者工具中 memory 来分析

    memory1-1.png

    堆快照闭包分类直指这个第三方库!!这个库是我们选择的第三方rpc数据服务库!!系统里面每一个接口调用都涉及到这个库!!嚯,赶快去看对应的文件

    memory6.png

    什么鬼!这个里面怎么有个定时器!!而且这个定时还不会被销毁!定时器里面还引用了外部变量!每一次数据服务都会创建实例,这样的话每次实例都会创建一个不会被销毁的的定时器,每个实例都会引用同一个外部变量!!!这样每次数据请求完,这个实例都不可能会被垃圾回收掉!!嗯,找到内存泄露的原因了!贼开心!

  • 根据上面的发现做出对应的修改后,发布测试。结果!结果!!结果!!!放置一夜后,应用内存竟然还在缓慢增长。。。what f**k!。。。容我奔溃下。。

  • 没办法,只能继续往下查。。。又是经过各种调试,各种对比。。然后发现下面这个


    memory2-1.png

    根据时间线来收集信息!发现了啥!!页面放置不动,只有数据服务不停的请求,然后更新数据。图中那些蓝色的线表明这块的创建的东西一直得不到回收!!然后点击对应的 Object 看发现全部指向第三方库的这个变量。。。又赶紧去看对应的文件。

    memory7.png

    晕呀!又是往外部变量赋值。。每一次调用数据服务创建的实例对象都会给这个变量对象添加属性,久而久之,这个变量会越来越大,越来越夸张。。而且由于这个对象一直被引用着,那么当初创建他的实例也一直得不到释放。。

  • 针对再一次的发现进行修改,长时间放置后观察发现内存保持稳定了,目前没有出现持续增长的情况。看样子,此次内存泄露问题应该得到解决了。

措施

  • 针对实时告警弹窗将 v-if 换成 v-show,不再重复进行 dom的创建与销毁,而是换成 dom 的隐藏与显示。另外,也对告警弹窗里面的表格展示的数量进行了控制。
  • 严格控制控制台的日志打印,不能输出很大的数据信息,尤其是对象类型的数据
  • 注释掉第三方依赖库里面的定时器(基于系统的情况,定时器实现的功能我们可以暂时不考虑)
  • 由于不想过多的修改第三方库代码,直接在原代码上采用循环覆盖的方式处理限制外部变量的大小

感想

  • 处理此类问题还是要细心,要耐心。其实最开始就用开发者工具监控查看了,但是可能最开始的思路或者说使用方式不对,走了很多弯路,浪费了很多时间。。
  • 选择第三方库(特别是项目需要的核心库),一定要选择使用频率高的库,选择比较成熟,大家都在用的库。。

相关文章

网友评论

    本文标题:前端内存泄露分析

    本文链接:https://www.haomeiwen.com/subject/qqosertx.html