在最近给客户交付的一个项目中,客户反映系统跑一段时间就会奔溃掉。我的第一直觉就是可能发生了内存泄漏。
复现bug
为了能够让bug复现,我让开发的小伙,先把模拟系统在本地跑起来。要知道,bug复现是很重要的。bug复现,能够让开发人员直观的感受到bug发生的过程。另外如果bug不能复现,程序员的反应可能是这样的:
我的这边是好的呀,没有什么问题。
哎呀,是不是你的使用方法不对啊。
看到了吗,如果bug不能复现,会直接极大的减少程序员想要修复bug的意愿的。
我后来问他,测试的怎么样,他说没有问题呀。我问题跑了多久,他说有一会儿吧,看没问题就关闭了。
一般来说,内存泄漏最终导致奔溃的需要挺长一段时间的,所以我告诉他一直跑,跑到奔溃为止。
后来第二天,他说果然奔溃了。
查找原因
因为更新了的版本才出现内存泄漏的问题,所以有理由怀疑,就是新增加的功能导致的内存泄漏。 新加的功能是,在一个TWaver的表格中,部分单元格中使用echart 图表来绘制。而由于代码中每次刷新的都会调用echart.init方法重新创建。导致原来创建的echart实例中创建的一些变量不能释放,所以这里怀疑,echart 如果反复删除并创建会导致内存泄漏。所以让开发人员改成了缓存的方案。
当然由于TWaver表格的内部机制,会在重新绘制的时候,移除上一次单元格的内容,并移除内容所有父子关系。这个问题导致了,缓存方案失效。 最终的解决方案是重写了这个移除方法,不解除移除内容的父子关系。
到此为止,应该问题是解决了。让小伙伴改进后,继续把模拟系统一直跑起来。 很不幸的是,第二天仍然奔溃。由于此时并不能直观的看出问题所在,所以需要使用一些技巧了。
简化程序
可以确定的是,还是表格的问题导致的内存泄漏,为了避免干扰,让小伙伴把表格的核心内容拉出来写两个一个demo。 这样就可以专注于表格相关问题的定位了。
在解决一些大项目的问题的时候,如果发现一些难定位的问题,可以考虑把程序简化,抽出有问题的代码部分写小的例子。方便问题的定位,排除不必要的干扰。
使用chrome的工具分析
首先把小伙伴写的小例子跑起来,然后打开chrome的控制台。点击memory标签:
memory标签
选择Allocation instrumentation on timeline,点击start,开始录制内存的记录情况,会发现有一些蓝色的条,永远不会变暗,表示这部分内存始终未被回收:
内存记录情况
点击其中的一个蓝条,可以查看局部的内存情况,如下图所示:
内存记录情况2
从中可以看到有array,system和Detached HTMLSpanElement。 我们知道Detached HTMLSpanElement表示已经脱离文档树的dom元素,它也是导致内存泄漏的一个经常的诱因,点击Detached HTMLSpanElement,查看详情:
Detached HTML Element
可以看到"_stringPool"这样的一个数组对象,里面放的就是Span元素,引用他的就是table,而且数量有372个之多,由于小例子使用的表格只有一行一列,这么大的数量,肯定是内存泄漏导致,直接在浏览器打印这个pool,可以看出这个数量确实在一直增加(看index的情况):
内存泄漏
至此,查到了是那个地方发生了内存泄漏。 根据这个线索,比较容易定位到程序的逻辑问题所在,并很快就解决了问题。
ps:同时还发现了table.__divPool也有内存泄漏的情况,通过同样的方式定位到了这个问题并最终解决。
问题出在小伙伴对于renderCell方法的重载和release方法的重载,这个涉及到TWaver的内部逻辑,此处就不详细说明了。