关于Poi读取Excel引发内存溢出问题的解决方法

时间:2021-11-04 13:17:24

前言

最近生产环境有个老项目一直内存报警,不时的还出现内存泄漏,导致需要重启服务器,已经严重影响正常服务了。

分析

1.dump内存文件

liunx使用如下命令:

?
1
./jmap -dump:format=b,file=heap.hprof pid

2.使用eclipse memory analysis进行分析

关于Poi读取Excel引发内存溢出问题的解决方法

异常如下:

?
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监控结果如下:

关于Poi读取Excel引发内存溢出问题的解决方法

usermodel模式下读取600kbexcel文件直接内存溢出,看了600kbexcel文件映射到内存中还是占用了不少内存;eventmodel模式下可以流畅的运行。

5.设置vm arguments:-xms200m -xmx200m

usermodel可以正常运行,使用java visualvm监控结果如下:

关于Poi读取Excel引发内存溢出问题的解决方法

eventmodel可以正常运行,使用java visualvm监控结果如下:

关于Poi读取Excel引发内存溢出问题的解决方法

usermodel模式和eventmodel模式都可以正常运行,但是很明显usermodel模式回收内存更加频繁,而且在cpu的占用上更高。

总结

通过简单的分析以及本地运行两种模式进行比较,可以看到usermodel模式下使用的简单的代码实现了读取,但是在读取大文件时cpu和内存都不理想;

而eventmodel模式虽然代码写起来比较繁琐,但是在读取大文件时cpu和内存更加占优。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:http://codingo.xyz/index.php/2017/06/29/poi_excel/