使用Apache POI出现“GC overhead limit exceeded”错误

12

我有13个.xlsx文件,每个文件大约有1000行。现在我想将它们合并为一个包含一个表格的.xlsx文件。我正在使用这里的代码 https://blog.sodhanalibrary.com/2014/11/merge-excel-files-using-java.html#.Vi9ns36rSUk。 这是我的代码(稍作更改,addSheet方法未更改)

try {
        FileInputStream excellFile1 = new FileInputStream(new File("tmp_testOut1000.xlsx"));
        XSSFWorkbook workbook1 = new XSSFWorkbook(excellFile1);
        XSSFSheet sheet1 = workbook1.getSheetAt(0);

        for(int i = 2; i < 14; i++){
            FileInputStream excellFile2 = new FileInputStream(new File("tmp_testOut" + i + "000.xlsx"));
            XSSFWorkbook workbook2 = new XSSFWorkbook(excellFile2);
            XSSFSheet sheet2 = workbook2.getSheetAt(0);
            System.out.println("add " + i);
            addSheet(sheet1, sheet2);
        }
        
        excellFile1.close();

        // save merged file
        System.out.println("merging");
        File mergedFile = new File("merged.xlsx");
        if (!mergedFile.exists()) {
            mergedFile.createNewFile();
        }
        FileOutputStream out = new FileOutputStream(mergedFile);
        System.out.println("write");
        workbook1.write(out);
        out.close();
        System.out.println("Files were merged succussfully");
    } catch (Exception e) {
        e.printStackTrace();
    }

所有文件都在加载和合并,但是在执行“write” sysout之后出现了以下问题:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at org.apache.xmlbeans.impl.store.Xobj.new_cursor(Xobj.java:1829)
at org.apache.xmlbeans.impl.values.XmlObjectBase.newCursor(XmlObjectBase.java:293)
at org.apache.xmlbeans.impl.values.XmlComplexContentImpl.arraySetterHelper(XmlComplexContentImpl.java:1151)
at org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTFontsImpl.setFontArray(Unknown Source)
at org.apache.poi.xssf.model.StylesTable.writeTo(StylesTable.java:424)
at org.apache.poi.xssf.model.StylesTable.commit(StylesTable.java:496)
at org.apache.poi.POIXMLDocumentPart.onSave(POIXMLDocumentPart.java:341)
at org.apache.poi.POIXMLDocumentPart.onSave(POIXMLDocumentPart.java:345)
at org.apache.poi.POIXMLDocument.write(POIXMLDocument.java:206)
at Start.main(Start.java:275)

我能做些什么?为什么会发生这种情况,如何预防?

4个回答

18

POI非常占用内存,因此在处理大型Excel文件时经常会出现内存不足的问题。

如果您可以加载所有原始文件,并且只有在编写合并文件时遇到麻烦,您可以尝试使用SXSSFWorkbook而不是XSSFWorkbook,并在添加一定量的内容后进行常规刷新(请参阅org.apache.poi.xssf.streaming包的poi文档)。这样,您就不必将整个生成的文件保存在内存中,而只需保存小部分内容。


有用的链接:https://poi.apache.org/components/spreadsheet/how-to.html 并且前往 SXSSF(流式用户模型API) - imnd_neel

3
尝试分配更多内存,例如。
java -Xmx8192m

另外,您可以尝试一次合并一个xlsx文件,而不是同时加载所有文件。

您还可以将此行移到您的for循环中:

excellFile1.close();

所以您立即关闭它。

2
这个问题是由下面的原因引起的: java.lang.OutOfMemoryError: GC overhead limit exceeded错误是JVM发出的信号,表明您的应用程序在垃圾收集方面花费了太多时间,但效果甚微。默认情况下,如果JVM在GC中花费的时间超过总时间的98%,并且在GC之后恢复的堆内存不到2%,它会配置为抛出此错误。 如果您只想忽略此问题,可以设置以下vm选项:
-XX:-UseGCOverheadLimit

请参考GC过载链接获取更多信息。

您还可以使用以下开关将更多堆内存分配给您的应用程序。对您的应用程序进行试运行一段时间,并确定为您的应用程序分配多少内存更好。

-Xms128m -Xmx512m(these switches sets the initial heap memory size to 128mb and Max memory to 512mb)

不改变虚拟机设置,这是否可能?如果不行,您能推荐一些其他的库来处理这个问题,而不会超出垃圾回收限制吗? - Marquess
我猜库不是问题,因为您在内存中保存了大量数据,我建议您查看源代码以优化解决方案和/或使用-Xms -Xmx开关修改应用程序的堆大小分配。 - Vivek Singh
哦,这两个选项都没能解决我的问题。这个文件只有2.9兆。其他文件都可以正常读取... - ka3ak

0

如果您可以避免使用方便但占用内存较多的工作簿API,而是使用逐行处理数据的流式处理逻辑,这将更加高效地利用内存。

特别是要注意使用XSSFReader.SheetIterator来循环遍历工作表。

最后,请仔细查看API的使用方式:XSSFSheetXMLHandler。用于处理工作表中的行。

请参阅此项目上的代码: https://github.com/jeevatkm/excelReader/blob/master/src/main/java/com/myjeeva/poi/ExcelReader.java

通过创建自己的new SheetContentsHandler...,您可以定义如何处理每一行。

这非常类似于SAX解析,它不会占用太多内存。

 private void readSheet(StylesTable styles, ReadOnlySharedStringsTable sharedStringsTable,
      InputStream sheetInputStream) throws IOException, ParserConfigurationException, SAXException {

    SAXParserFactory saxFactory = SAXParserFactory.newInstance();
    XMLReader sheetParser = saxFactory.newSAXParser().getXMLReader();

    ContentHandler handler =
        new XSSFSheetXMLHandler(styles, sharedStringsTable, sheetContentsHandler, true);

    sheetParser.setContentHandler(handler);
    sheetParser.parse(new InputSource(sheetInputStream));
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接