项目中为了获取用户导入的模板属性, 需要直接读取用户上传的excel文件,创建XSSWorkbook对象, 用户上传了一个12M大小的文件, 导出项目读取Excel时占用内存达到3G, 导致了项目OOM, 然后重启
分析过程:
1, 使用Java VisualVM加载dump文件, 查询对象占用内存
查看占用较多内存传对象是 org.apache.xmlbeans.impl.store.Xobj$AttrXobj, org.apache.xmlbeans.impl, 查资料发现这两个对象是POI中的对象, 初步定位为POI进行导入导出操作时导致的内存溢出
2, 使用MemoryAnalyzer加载dump文件, 查看Thread Stack线程栈信息
查询线程栈信息, 依旧指向的是POI中的代码, 查看下面几行就指向了业务代码, 找到代码中指向的行, 代码如下:
这里使用了POI的userModel模式, 直接加载excel文件到内存中创建XSSFWorkbook对象, 占用内存很大, 极有可能造成内存溢出
联系用户, 获取到了用户导入的Excel文件, 文件有12M, 然后在本地复现内存溢出的场景,
在idea中Add VM Options设置启动内存和最大内存占用:
在Excel导入接口中上传用户的12M文件, 用Java VisualVM监测内存占用情况:
debug到下面这行代码时, 出现内存和CPU占用飙升, 控制台打印报错OutOfMemoryError
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(templateFile.getInputStream())
控制台打印如下:
查资料了解了POI的userModel占用内存较大
1、usermodel方式,就是我们一般使用的方式,这种方式可以读可以写,但是CPU和内存消耗非常大
2、eventmodel方式,基于事件驱动,SAX的方式解析excel(.xlsx是基于OOXML的),CPU和内存消耗非常低,但是只能读不能写,塔式应用程序一边读取数据,一边处理数据。
SAX模型最大的优点是内存消耗小,因为整个文档无需一次加载到内存中,这使SAX解析器可以解析大于系统内存的文档。
SAX的工作原理简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,
由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束
最后修改获取Excel文件的属性代码如下:
总结主要是通过两点防止导入Excel文件导致的OOM
(1) 将接口上传文件大小的限制降低为5M
(2) 不要使用POI中的usermodel方式读取Excel文件(API简单但占用内存极大), 使用eventmodel方式, SAX读取Excel文件比较解决内存