记一次内存溢出处理经历

最近刚上线一个项目,没过多久测试反馈说导出excel的功能很卡,卡着卡着网站就崩了。

用jstat查了一下,发现导出大量数据的时候,老年代的占比一直在升高,没有降低过,而且cpu一直在狂转,感觉是在不停gc,但是又没有释放多少空间。用jmap查看了一下,发现占用内存最多的是apache命名空间下的类(查了一下好像是poi相关的类)和导数据相关的实体。分析了一下线上数据,发现这个报表的功能所涉及的数据量非常大,一共10来个sheet页,每个sheet页几十万到上百万的数据量。
看了一下源代码,由于相关数据是来源于多个数据源,通过接口返回,在内存中做的计算。所以就制定了一个初步的修改方案:
1、由于之前处理整个导出是放在一个大方法中的,大的方法里面会调用各个接口取到数据然后填充数据到各个sheet的。这样存在一个问题,各个接口返回的数据要等到整个方法结束才能被垃圾回收释放掉。所以需要把各个sheet页的处理单独拆成多个子方法,这样每个sheet的填充后相关数据就可以及时释放。
2、之前代码中没有考虑数据量,很多地方使用list做查找的,小的数据量下这样没有问题,针对多个几十万、上百万的集合这样做效率就太低了,所以相关集合改为 map。
3、优化取数据的接口,只返回所需字段,减少传输量。
4、针对一次返回太多数据的接口,可以优化成两步,先只返回id列,然后把返回的id按照一定大小分页查询,这样可以避免接口服务器一次处理太多数据卡顿。
5、很多地方都在重复查询相同的数据,可以通过定义RequestScope的缓存对象解决。
6、有很多地方为了重用计算逻辑,存在深克隆对象的情况,可以简单重构一下,计算的地方依赖接口,实体只需要实现接口,就不需要转换实体对象也能保证计算逻辑可重用。
7、上面提到的大量poi相关的类,经过反复测试,发现是单元格上面添加的样式导致的,经过同用户沟通,不使用样式用户也是可以接受的,只是需要输出的时候转换一下日期、金额等格式。
经过以上修改,导出功能总算正常了,内存也一直很平稳,时间也降下来了。

上一篇:Javascript-有没有办法在多个电子主过程之间进行通讯?


下一篇:如何使用命名管道(C服务器,C#客户端)