引言
在开发综合治理平台态势概览的大屏页面的过程,遇到了页面崩溃的问题,本帖子记录了崩溃的原因分析和解决方案。
问题
打开综合治理平台,进入态势概览页面,停留在此页面一段时间,会出现如下图所示的页面崩溃的情况。
原因分析
注:以下操作环境建议在浏览器隐身模式下进行,防止其他因素干扰
使用工具
根据页面崩溃的提示,可以初步判定是页面内存溢出导致的崩溃,为了验证内存是否溢出,可以使用 Chrome 浏览器自带的工具分析验证。这里介绍三种工具的使用,可以结合实际需求来使用。
- 任务管理器 1) 打开方式:
- 2) 界面:
- 3)使用方式: 任务管理器的使用方法最为简单,打开需要分析的页面,直接观察内存占用空间与 JavaScript 使用的内存即可。如果这两个数据持续上升,说明内存正在泄漏。
- 开发者工具 Performance 面板 1) 打开方式: 按 F12 打开开发者工具,选择 Performance 栏 2) 界面:
- 3) 使用方式: 打开需要分析的页面,等页面稳定下来后,点击 Performance 左上角的录制按钮开始录制,它会保存下页面的快照、JS Heap、Document、Nodes、Listener、GPU Memory等信息。录制一段时间(最好在一分钟以上)后,停止录制,等待工具生成报告。
- 开发者工具 Memory 面板 1) 打开方式: 按 F12 打开开发者工具,选择 Memory 栏 2) 界面:
- 3) 使用方式: 这个工具的使用方式最为复杂,这里只简单介绍下 Heap snapshot 的使用,感兴趣的可以自行搜索其他使用方式。 首先,打开需要分析内存的页面,点击工具左上方的录制按钮,生成分析报告。 其次,进行一些可能导致内存上升的操作后,再次点击录制按钮,生成分析报告。 最后,我们有了两份分析报告,通过菜单栏的下拉框选择 Comparison 过滤分析结果。观察 #New、#Deleted、#Delta 这三列,分别代表新增对象数、删除对象数、新增数与删除数的差值,找到那些只有新增,没有删除的对象,看看是被谁引用了,据此来找到可能导致内存溢出的代码。
确定内存溢出原因
打开态势概览页面后,通过任务管理器观察页面使用的内存,发现内存是持续上升的,这时再通过 Performance 工具进一步分析。 以下是 Performance 的分析结果:
通过分析结果可以看到,内存资源在持续上升,再进一步观察结果,可以发现内存上升是存在一个阶梯式的上升周期的,为什么会产生这种现象呢?放大内存上升的部分进一步分析看看:
上图显示了内存上升部分细节,把鼠标移动到页面快照上,可以清楚地看到,当上一个大屏页面轮播到下一个页面时,内存就会上升并且不会再下降到之前的水平。现在已经有理由怀疑是页面轮播引起的内存溢出,所以,通过暂停页面轮播,再进行一次 Performance 分析,看看分析结果:
通过上图可以看到,暂停页面轮播后,并没有明显的上升趋势,说明浏览器可以正常回收内存,并没有溢出。至此,已经可以确定当页面轮播时内存会溢出。
分析内存溢出对象
经过第二步的分析,已经知道了大屏页面切换会导致内存升高,利用这个证据,用 Memory 工具去进一步分析,找到那些被引用本该被释放,但实际没有的释放的对象。 首先,打开态势概览页面,先暂停页面轮播切换,停留在总体态势页面,待页面加载完成,然后打开 Memory 工具,点击录制按钮分析总体态势页面的内存。分析完成后,手动切换到风险态势页面或者其他大屏页面,再切换回总体态势页面,然后在 Memory 工具中再次点击录制按钮分析页面切换之后的内存。完成以上操作之后,就得到了两份分析报告,分别是内存上升前和上升后的,在 Memory 工具的菜单栏下拉框中选择 Comparison,看看到底是哪些家伙占用了内存!
来分析上面的结果图,首先,页面上有各种类型的对象,点击对象可以看到对象具体的引用信息,我们的任务是通过对象引用信息找到一些蛛丝马迹。我们可以搜索 detached 开头归类的对象,也就是那些在内存中但是没有在页面进行渲染的元素。选择一个,可以看到它的详细引用信息:
很明显,ehcarts 引用了这个对象,而这个对象连同它的引用,理应是在页面切换之后被销毁了的,既然他没有销毁,说明我们的代码是有问题的。接下来要做的是,找出 ehcarts 引用的对象没有被销毁的原因,修改相关代码,再验证。
解决方案
使用正确的 echarts 实例销毁方法
根据上面的原因分析,我们知道是 echarts 引用的对象没有正常被销毁,那么很简单,我们只要尝试正确销毁 echarts 实例就好了。进入到我们的 ehcarts 组件代码,定位到 beforDestory 钩子,可以看到,已经有一段代码对 echarts 实例进行释放了。
进入 echarts 官网查询销毁实例的相关 api,发现.clear()
方法只是清空了实例,并没有销毁,而.dispose()
方法才会销毁实例。
答案到这里已经呼之欲出了,我们项目中之前一直用的是.clear()
方法清空 echarts 实例对象,而不是用.dispose()
销毁,所以 echarts 实例并没有被正常销毁,当我们频繁地切换页面的时候,echarts 实例就会不断的累加,占用的内存也会随之增加。所以,这里建议,以后我们封装 echarts 组件的时候,统一使用.dispose()
方法销毁组件。
页面隐藏时停止定时器任务
你以为到这里就结束了吗?事情没有那么简单!在搜索内存溢出解决方案的时候,在网上看到了一篇文章:
再次通过 Performance 工具分析验证,结果如下:
果然,内存又在持续增加,那么就使用网上分享的方法解决这个问题,添加一个标签切换的监听函数,在离开页面时,把页面的定时器清除,回来页面时重新开启定时器,这样就可以了。
结果
- 保持在态势概览页面,并开启轮播页面,使用 Permermance 工具进行内存分析 结果:内存保持在平稳状态,没有上升,页面没有崩溃
- 进入态势概览页面,开启轮播页面后,切换到其他标签页或最小化浏览器 结果:内存保持在平稳状态,没有上升,页面没有崩溃
- 保持在态势概览页面,并开启轮播页面,不做其他操作,挂机一天一夜。 结果:内存保持在 2G 左右,页面没有崩溃
总结
通过此次的内存溢出分析,我们认识了一些内存分析工具及内存分析方法,也发现了代码中不足的地方,最后通过正确的方法解决了内存溢出的问题。希望这篇文章可以对大家日后的工作有所帮助。当然,这只是很小一部分,也可能有不正确的地方,欢迎大家提出疑问一起探讨。