关于Poi读取Excel引发内存溢出问题的解决方法
前言
最近生产环境有个老项目一直内存报警,不时的还出现内存泄漏,导致需要重启服务器,已经严重影响正常服务了。
分析
1.dump内存文件
liunx使用如下命令:
./jmap -dump:format=b,file=heap.hprof pid
2.使用eclipse memory analysis进行分析
异常如下:
at org.apache.poi.xssf.usermodel.xssfrow.<init>(lorg/openxmlformats/schemas/spreadsheetml/x2006/main/ctrow;lorg/apache/poi/xssf/usermodel/xssfsheet;)v (xssfrow.java:68) at org.apache.poi.xssf.usermodel.xssfsheet.initrows(lorg/openxmlformats/schemas/spreadsheetml/x2006/main/ctworksheet;)v (xssfsheet.java:157) at org.apache.poi.xssf.usermodel.xssfsheet.read(ljava/io/inputstream;)v (xssfsheet.java:132) at org.apache.poi.xssf.usermodel.xssfsheet.ondocumentread()v (xssfsheet.java:119) at org.apache.poi.xssf.usermodel.xssfworkbook.ondocumentread()v (xssfworkbook.java:222) at org.apache.poi.poixmldocument.load(lorg/apache/poi/poixmlfactory;)v (poixmldocument.java:200) at org.apache.poi.xssf.usermodel.xssfworkbook.<init>(ljava/io/inputstream;)v (xssfworkbook.java:179)
poi在加载excel引发了内存泄漏,中间创建了大量的对象,占用了大量的内存
3.查看上传的excel大小
经查看发现很多excel大小在9m的文件
4.查看代码poi读取excel的方式
发现使用的是用户模式,这样会占用大量的内存;poi提供了2中读取excel的模式,分别是:
- 用户模式:也就是poi下的usermodel有关包,它对用户友好,有统一的接口在ss包下,但是它是把整个文件读取到内存中的,
对于大量数据很容易内存溢出,所以只能用来处理相对较小量的数据; - 事件模式:在poi下的eventusermodel包下,相对来说实现比较复杂,但是它处理速度快,占用内存少,可以用来处理海量的excel数据。
经上面分析基本可以确定问题出在使用poi的用户模式去读取excel大文件,导致内存泄漏。
本地重现
下面模拟一个600kb大小的excel(test.xlsx),分别用两种模式读取,然后观察内存波动;
1.需要引入的库maven:
<dependencies> <dependency> <groupid>org.apache.poi</groupid> <artifactid>poi-ooxml</artifactid> <version>3.6</version> </dependency> <dependency> <groupid>com.syncthemall</groupid> <artifactid>boilerpipe</artifactid> <version>1.2.1</version> </dependency> </dependencies>
2.用户模式代码如下:
import java.io.file; import java.io.fileinputstream; import java.io.ioexception; import java.io.inputstream; import org.apache.poi.ss.usermodel.cell; import org.apache.poi.ss.usermodel.row; import org.apache.poi.ss.usermodel.sheet; import org.apache.poi.ss.usermodel.workbook; import org.apache.poi.xssf.usermodel.xssfworkbook; public class usermodel { public static void main(string[] args) throws interruptedexception { try { thread.sleep(5000); system.out.println("start read"); for (int i = 0; i < 100; i++) { try { workbook wb = null; file file = new file("d:/test.xlsx"); inputstream fis = new fileinputstream(file); wb = new xssfworkbook(fis); sheet sheet = wb.getsheetat(0); for (row row : sheet) { for (cell cell : row) { system.out.println("row:" + row.getrownum() + ",cell:" + cell.tostring()); } } } catch (ioexception e) { e.printstacktrace(); } } thread.sleep(1000); } catch (exception e) { e.printstacktrace(); } } }
3.事件模式代码如下:
import java.io.inputstream; import org.apache.poi.openxml4j.opc.opcpackage; import org.apache.poi.xssf.eventusermodel.xssfreader; import org.apache.poi.xssf.model.sharedstringstable; import org.apache.poi.xssf.usermodel.xssfrichtextstring; import org.xml.sax.attributes; import org.xml.sax.contenthandler; import org.xml.sax.inputsource; import org.xml.sax.saxexception; import org.xml.sax.xmlreader; import org.xml.sax.helpers.defaulthandler; import org.xml.sax.helpers.xmlreaderfactory; public class eventmodel { public void processonesheet(string filename) throws exception { opcpackage pkg = opcpackage.open(filename); xssfreader r = new xssfreader(pkg); sharedstringstable sst = r.getsharedstringstable(); xmlreader parser = fetchsheetparser(sst); inputstream sheet2 = r.getsheet("rid1"); inputsource sheetsource = new inputsource(sheet2); parser.parse(sheetsource); sheet2.close(); } public xmlreader fetchsheetparser(sharedstringstable sst) throws saxexception { xmlreader parser = xmlreaderfactory.createxmlreader("org.apache.xerces.parsers.saxparser"); contenthandler handler = new sheethandler(sst); parser.setcontenthandler(handler); return parser; } private static class sheethandler extends defaulthandler { private sharedstringstable sst; private string lastcontents; private boolean nextisstring; private sheethandler(sharedstringstable sst) { this.sst = sst; } public void startelement(string uri, string localname, string name, attributes attributes) throws saxexception { if (name.equals("c")) { system.out.print(attributes.getvalue("r") + " - "); string celltype = attributes.getvalue("t"); if (celltype != null && celltype.equals("s")) { nextisstring = true; } else { nextisstring = false; } } lastcontents = ""; } public void endelement(string uri, string localname, string name) throws saxexception { if (nextisstring) { int idx = integer.parseint(lastcontents); lastcontents = new xssfrichtextstring(sst.getentryat(idx)).tostring(); nextisstring = false; } if (name.equals("v")) { system.out.println(lastcontents); } } public void characters(char[] ch, int start, int length) throws saxexception { lastcontents += new string(ch, start, length); } } public static void main(string[] args) throws exception { thread.sleep(5000); system.out.println("start read"); for (int i = 0; i < 100; i++) { eventmodel example = new eventmodel(); example.processonesheet("d:/test.xlsx"); thread.sleep(1000); } } }
具体代码来源:
4.设置vm arguments:-xms100m -xmx100m
usermodel运行结果直接报outofmemoryerror,如下所示:
exception in thread "main" java.lang.outofmemoryerror: gc overhead limit exceeded at java.lang.string.substring(string.java:1877) at org.apache.poi.ss.util.cellreference.separaterefparts(cellreference.java:353) at org.apache.poi.ss.util.cellreference.<init>(cellreference.java:87) at org.apache.poi.xssf.usermodel.xssfcell.<init>(xssfcell.java:105) at org.apache.poi.xssf.usermodel.xssfrow.<init>(xssfrow.java:68) at org.apache.poi.xssf.usermodel.xssfsheet.initrows(xssfsheet.java:157) at org.apache.poi.xssf.usermodel.xssfsheet.read(xssfsheet.java:132) at org.apache.poi.xssf.usermodel.xssfsheet.ondocumentread(xssfsheet.java:119) at org.apache.poi.xssf.usermodel.xssfworkbook.ondocumentread(xssfworkbook.java:222) at org.apache.poi.poixmldocument.load(poixmldocument.java:200) at org.apache.poi.xssf.usermodel.xssfworkbook.<init>(xssfworkbook.java:179) at zh.exceltest.usermodel.main(usermodel.java:23)
eventmodel可以正常运行,使用java visualvm监控结果如下:
usermodel模式下读取600kbexcel文件直接内存溢出,看了600kbexcel文件映射到内存中还是占用了不少内存;eventmodel模式下可以流畅的运行。
5.设置vm arguments:-xms200m -xmx200m
usermodel可以正常运行,使用java visualvm监控结果如下:
eventmodel可以正常运行,使用java visualvm监控结果如下:
usermodel模式和eventmodel模式都可以正常运行,但是很明显usermodel模式回收内存更加频繁,而且在cpu的占用上更高。
总结
通过简单的分析以及本地运行两种模式进行比较,可以看到usermodel模式下使用的简单的代码实现了读取,但是在读取大文件时cpu和内存都不理想;
而eventmodel模式虽然代码写起来比较繁琐,但是在读取大文件时cpu和内存更加占优。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。