前言
最近生产环境有个老项目一直内存报警,不时的还出现内存泄漏,导致需要重启服务器,已经严重影响正常服务了。
分析
1.dump内存文件
liunx使用如下命令:
1
|
./jmap -dump:format=b,file=heap.hprof pid |
2.使用eclipse memory analysis进行分析
异常如下:
1
2
3
4
5
6
7
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
|
<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.用户模式代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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.事件模式代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
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 ); } } } |
具体代码来源:http://poi.apache.org/spreadsheet/how-to.html#xssf_sax_api
4.设置vm arguments:-xms100m -xmx100m
usermodel运行结果直接报outofmemoryerror,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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和内存更加占优。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://codingo.xyz/index.php/2017/06/29/poi_excel/