前言
最近生产环境有个老项目一直内存报警,不时的还出现内存泄漏,导致需要重启服务器,已经严重影响正常服务了。
分析
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/