文档章节

Poi读取Excel引发的内存溢出

ksfzhaohui
 ksfzhaohui
发布于 2017/06/29 17:17
字数 1140
阅读 1557
收藏 98
点赞 4
评论 5
poi

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

分析
1.dump内存文件
liunx使用如下命令:

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

2.使用Eclipse Memory Analysis进行分析

异常如下:

t 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);
        }
    }
}

具体代码来源:http://poi.apache.org/spreadsheet/how-to.html#xssf_sax_api

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和内存更加占优。

个人博客:codingo.xyz

© 著作权归作者所有

共有 人打赏支持
ksfzhaohui

ksfzhaohui

粉丝 296
博文 126
码字总数 153828
作品 3
南京
高级程序员
加载中

评论(5)

writeademo
writeademo
:+1:赞一个
抢小孩糖吃
抢小孩糖吃
不错
iblur
iblur
所以我们一般都是上传excel限制大小的
1363435084
1363435084
赞一个!
蓝水晶飞机
蓝水晶飞机
赞大神的研究精神!共勉
POI读取文件的最佳实践

POI是 Apache 旗下一款读写微软家文档声名显赫的类库。应该很多人在做报表的导出,或者创建 word 文档以及读取之类的都是用过 POI。POI 也的确对于这些操作带来很大的便利性。我最近做的一个...

neal ⋅ 2017/11/27 ⋅ 0

求java解析excel大文件的解决方案

一个excel有5/6个sheel,sheet多的大概有3万条数据以上,整个文件15M左右。 文件可能是.xls或xlsx文件。 需求:解析每个sheet进行入库,最好能精确获取到某一行某一列的值,这样可以配置每一...

风中海岸 ⋅ 2017/08/05 ⋅ 3

POI处理excel2007内存溢出问题

项目中遇到数据导入、导出用excle操作的问题,数据量在W级别,因03版有6W+的限制,系统统一采用07版excel来做,采用POI进行处理,在导入、导出的时候都遇到的内存溢出的问题,导入方面主要参...

sjzmlb ⋅ 2015/05/13 ⋅ 0

POI解析excel的漏洞(CVE-2014-3574)

一、概述 最早的时候,java开发人员在操作excel的时候,用的最多的框架应该是poi、jxl。随着office的不断发展,office2007开始支持openXML的协议,后续陆续出现了新的框架支持操作office,如...

漫天的沙 ⋅ 2015/06/02 ⋅ 15

Excel解析与数据生成(支持JXL,POI)

最近项目中需要用到Excel解析与生成Excel,但现有系统中没有进行解析工具封装于是便封装一个Excel工具包,支持JXL工具包与POI工具包解析与生成数据,POI支持2003与2007。 Excel解析目前网上有...

皮蛋猫 ⋅ 2013/02/01 ⋅ 0

Android操作文档系列2- 操作Excel

1 开发调研 1.1 需求描述MS的电子表格(Excel)是Office的重要成员,是保存统计数据的一种常用格式。作为办公文档,势必要涉及到的电子文档的交换,Excel是一种在企业中非常通用的文件格式,...

我家有宝 ⋅ 2016/09/27 ⋅ 0

使用 poi导大量数据到excel 2007导致内存溢出

我使用的是poi 3.9。导数据到excel 2007。编程环境是eclipse,使用maven进行管理,项目是ssh项目。 主要依赖jar包: org.apache.poipoi3.9org.apache.poipoi-ooxml...

moyiguke ⋅ 2013/06/07 ⋅ 4

Java程序员从笨鸟到菜鸟之(一百零三)java操作office和pdf文件(一)java读取word,excel和pdf文档内容

在平常应用程序中,对office和pdf文档进行读取数据是比较常见的功能,尤其在很多web应用程序中。所以今天我们就简单来看一下java对word、excel、pdf文件的读取。本篇博客只是讲解简单应用。如...

长平狐 ⋅ 2012/11/12 ⋅ 0

Apache POI 3.8 发布

Apache POI 发布了 3.8 正式版,该版本主要改进内容包括: NPOIFS: NIO 驱动 API 用于读取 OLE2 文件系统,占用内存低 SXSSF: 使用很低的内存去处理非常大的电子表格 poi-excelant: 提供 An...

红薯 ⋅ 2012/03/29 ⋅ 3

流实现低内存下读取大量数据和处理并存储大文件

昨天看到有朋友A在问,“我的程序一旦导出稍微大点的Excel一定会OOM,你们的应用导出多少数据量都没有问题,如何做到的?”。这里涉及到两个问题:读取和写入的内存占用。其实业务很简单:从...

tivenwang ⋅ 2016/11/19 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Linux中的端口大全

1 被LANA定义的端口 端口 名称 描述 1 tcpmux TCP 端口服务多路复用 5 rje 远程作业入口 7 echo Echo 服务 9 discard 用于连接测试的空服务 11 systat 用于列举连接了的端口的系统状态 13 d...

寰宇01 ⋅ 11分钟前 ⋅ 0

Confluence 6 如何备份存储文件和页面信息

备份的 ZIP 文件包含有 entities.xml,这个 XML 文件包含有 Confluence 的所有页面内容和存储附件的目录。 备份 Zip 文件结构 页面的附件是存储在附件存储目录中的,通过页面和附件 ID 进行识...

honeymose ⋅ 14分钟前 ⋅ 0

【每天一个JQuery特效】根据状态确定是否滑入或滑出被选元素

主要效果: 本文主要采用slideToggle()方法实现以一行代码同时实现以展开或收缩的方式显示或隐藏被选元素。 主要代码如下: <!DOCTYPE html><html><head><meta charset="UTF-8">...

Rhymo-Wu ⋅ 18分钟前 ⋅ 0

度量.net framework 迁移到.net core的工作量

把现有的.net framework程序迁移到.net core上,是一个非常复杂的工作,特别是一些API在两个平台上还不能同时支持。两个类库的差异性,通过人工很难识别全。好在微软的工程师们考虑到了我们顾...

李朝强 ⋅ 23分钟前 ⋅ 0

请不要在“微服务”的狂热中迷失自我!

微服务在过去几年一直是一个非常热门的话题(附录1)。何为“微服务的疯狂”,举个例子: 众所周知,Netflix在DevOps上的表现非常棒。Netfix可以做微服务。因此:如果我做微服务,我也将非常...

harries ⋅ 24分钟前 ⋅ 0

oAuth2 升级Spring Cloud Finchley.RELEASE踩坑分享

背景 6.19号,spring团队发布了期待已久的 Spring Cloud Finchley.RELEASE 版本。 重要变化: 基于Spring Boot 2.0.X 不兼容 Spring Boot 1.5.X 期间踩过几个坑,分享出来给大伙,主要是关于...

冷冷gg ⋅ 54分钟前 ⋅ 0

OSChina 周一乱弹 —— 理发师小姐姐的魔法

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @冰冰棒- :分享田馥甄的单曲《My Love》 《My Love》- 田馥甄 手机党少年们想听歌,请使劲儿戳(这里) @Li-Wang :哎,头发又长了。。。又要...

小小编辑 ⋅ 今天 ⋅ 8

Kafka1.0.X_消费者API详解2

偏移量由消费者管理 kafka Consumer Api还提供了自己存储offset的功能,将offset和data做到原子性,可以让消费具有Exactly Once 的语义,比kafka默认的At-least Once更强大 消费者从指定分区...

特拉仔 ⋅ 今天 ⋅ 0

NEO智能合约之发布和升级(二)

接NEO智能合约之发布和升级(一),我们接下来说说智能合约的升级功能。 一 准备工作 合约的升级需要在合约内预先设置好升级接口,以方便在升级时调用。接下来我们对NEO智能合约之发布和升级...

红烧飞鱼 ⋅ 今天 ⋅ 0

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部