分析 Echarts 图表渲染导致的内存泄漏问题 - 附解决方案

一. 引言

在今年某个可视化大屏项目中,出现了一个问题。项目在运行一段时间后,页面出现了崩溃,而且是大概运行几天之后,因为大屏项目是部署到客户现场大屏,长时间运行不关闭。报错问题如下图所示:

由于这个大屏页面是使用 Echarts 图表渲染的,整个页面就一个渲染地图的功能,没有多少耗费内存的操作。而 Echarts 作为一款强大的数据可视化库,被广泛应用于各种项目中,因此 Echarts 的稳定性也是经过广大开发者验证的,不会出现明显的 bug 问题。

然而,我们也知道,如果页面随着图表数量的增加和动态更新的需求,我们必然会考虑 Echarts 图表渲染导致的内存泄漏问题。因此,上面的问题我们按照这个猜想来往下走,认为 Echarts 图表渲染导致了内存泄漏,那么 Echarts 的什么操作会导致内存泄漏、甚至到页面崩溃呢?

本文将深入分析这一问题,并提供解决方案。

二. 问题背景

猜测原因

当我们使用 Echarts 来渲染大量图表时,会发现页面的内存占用不断增加,最终导致页面卡顿甚至崩溃。这是由于 Echarts 在图表渲染过程中产生的内存泄漏导致的。

而奇怪的是,自己在本地中测试的时候,从未有过崩溃的现象?但是在客户现场就出现了这种问题。

内存占用检测

为了能够实时监测网站运行过程中,我们在 Chrome 浏览器打开了内存性能分析,便于实时查看内存占用情况,发现运行内存占用并不是太大,我们监测过一段时间后发现,确实内存有时在稳定的增加,但是这点内存占用还不至于导致系统崩溃。

内存泄漏原因分析

  • 嵌套事件绑定:在 Echarts 图表中,每个图表都会绑定各种交互事件。如果我们正确地解绑这些事件,则会导致事件监听器无法垃圾回收,从而造成内存泄漏。

  • 大量的图表实例:如果我们在动态更新图表的过程中,每次都创建新的图表实例而不销毁旧的实例,就会导致内存占用不断增加。

  • 定时器未清理:如果我们在更新图表的过程中使用了定时器来控制刷新频率,但未能正确清理这些定时器,就可能导致内存泄漏。

三. 第一轮解决方案

因为可视化大屏项目代码不是我直接写的,我当时只不过是被临时抽过去提过几个建议而已,所以并没有深入的研读项目代码,所以这一轮解决方案可以定义为是在浪费时间,不过也并不是一点经验没有,一起来看一下吧。

初步解决

为了解决 Echarts 图表渲染导致的内存泄漏问题,我们采取以下措施:

  • 在绑定事件监听器时,要确保正确解绑,可以使用 Echarts 提供的off方法来取消事件定。

  • 在图表组件销毁之前,务必使用 dispose 函数销毁图表实例,以确保释放内存。

  • 在动态更新图表时,尽量复用现有的图表实例,而不是每次都创建新的实例。可以使用 Echarts 提供的setOption方法来更新图表数据和配置。

  • 如果使用了定时器来控制图表的刷新频率,必须在组件销毁前清定时器,可以使用clearIntervalclearTimeout来停止定时器。

  • 正确管理图表组件的生命周期,尽量避免不必要的渲染和更新操作。

初步验证

按照以上的初步解决方案,我们对流程进行了初步优化,主要进行了事件监听器的绑定与解绑优化、销毁定时器、dispose 函数销毁图表实例等操作。

等这些优化操作完成后,我们又部署到自己的系统进行初步验证,发现内存占用确实变的小了,但是等到运行长时间来看,内存还是有上升的趋势。因此我断定,没有找到根本原因解决。

果然,运行了大概有五天的时间,浏览器还是顶不住了,系统再一次不工作了。

四. 第二轮解决方案

可能原因

由于这个大屏项目是其他同事开发的,具体的代码我并不太清楚。因此,先前只是提供了一些建设性的建议,没想到同事修改完成后还是没有解决了根本问题。所以可能必须要完全读懂项目的代码才能找到根本原因。

果然,看了几遍代码后发现了一些端倪,页面部署上之后是不会再进行操作了,数据会自动定时刷新,地图数据也会自动刷新,因此,问题就出现在这了。

猜想

多次调用 Echarts.init,项目中这一段代码写的确实有问题,写了个定时器,每次刷新数据时,都需要调用 Echarts init,并且销毁时 clear 和 dispose 方法使用不当造成,定时器循环重绘 Echarts 图表导致内存一直升高,最终导致了浏览器崩溃。

解决方案

通过 Echarts init 方法创建 Echarts 实例,如果代码没有做优化,echarts 实例就会越来越多,占用大量内存,有以下两种方法可以避免这种情况:

第一种:使用 Echarts init 之前先判断是否存在实例

js 复制代码
const chart = Echarts.getInstanceByDom(document.getElementById(dom));
if (chart === undefined) {
  chart = Echarts.init(document.getElementById(dom));
}

第二种:如果 Echarts 存在,先 dispose 销毁后,再调用 init

js 复制代码
const chart = Echarts.getInstanceByDom(document.getElementById(dom));
if (chart) {
  Echarts.dispose(chart);
}
chart = Echarts.init(document.getElementById(dom));

验证

使用上述的代码进行优化,再结合第一轮的代码优化后。系统又重新部署了,监测系统内存状态,初始的内存占用大小和之前的相差不大。不过观察一段时间后,内存没有持续升高的趋势,还算比较稳定。又这样运行了大概有一周左右,发现内存占用仍然稳定,系统也没有出现过崩溃的问题。因此可以断定,应该是优化好了。

五. 总结

Echarts 图表渲染导致的内存泄漏问题是我们在使用 Echarts 时经常遇到的挑战之一。

在 Echarts 做图表开发时,避免内存泄漏的几点操作主要有以下几个方面:

  • 多次调用 Echarts.init 会导致内存泄漏,应当在恰当时机销毁已经存在的 Echarts 实例,使用 clear() 和 dispose() 手动清理,区别在于:

    • clear()不会销毁实例,只是重新绘制图形,
    • dispose()会销毁实例,需要重新构建 ECharts 对象
  • 在动态更新图表时,尽量复用现有的图表实例,而不是每次都创建新的实例。可以使用 Echarts 提供的setOption方法来更新图表数据和配置。

  • 在绑定事件监听器时,要确保正确解绑,可以使用 Echarts 提供的off方法来取消事件。

通过深入分析内存泄漏的原因,并采取相应的解决方案,我们可以有效地解决这一问题,确保应用的稳定性和性能。

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试