将大量数据写入excel:超出了GC开销限制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将大量数据写入excel:超出了GC开销限制相关的知识,希望对你有一定的参考价值。

我有一个从MongoDB读取的字符串列表(~200k行)然后我想用Java代码将它写入excel文件:

public class OutputToExcelUtils {

    private static XSSFWorkbook workbook;
    private static final String DATA_SEPARATOR = "!";

    public static void clusterOutToExcel(List<String> data, String outputPath) {

        workbook = new XSSFWorkbook();
        FileOutputStream outputStream = null;

        writeData(data, "Data");


        try {
            outputStream = new FileOutputStream(outputPath);            
            workbook.write(outputStream);
            workbook.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void writeData(List<String> data, String sheetName) {

        int rowNum = 0;
        XSSFSheet sheet = workbook.getSheet(sheetName);     
        sheet = workbook.createSheet(sheetName);


        for (int i = 0; i < data.size(); i++) {
            System.out.println(sheetName + " Processing line: " + i);
            int colNum = 0;
            // Split into value of cell
            String[] valuesOfLine = data.get(i).split(DATA_SEPERATOR);

            Row row = sheet.createRow(rowNum++);

            for (String valueOfCell : valuesOfLine) {
                Cell cell = row.createCell(colNum++);
                cell.setCellValue(valueOfCell);
            }
        }
    }

}

然后我收到一个错误:

线程“main”中的异常java.lang.OutOfMemoryError:在org.apache.xmlbeans.impl.store.Locale上的org.apache.xmlbeans.impl.store.Cur $ Locations。(Cur.java:497)超出了GC开销限制。(Locale.java:168)org.apache.xmlbeans.impl.store.Locale.getLocale(Locale.java:242)org.apache.xmlbeans.impl.store.Locale.newInstance(Locale.java:593)位于org.openxmlformas.schemas.spreadsmlml.x2006.main的org.apache.poi.POIXMLTypeLoader.newInstance(POIXMLTypeLoader.java:132)的org.apache.xmlbeans.impl.schema.SchemaTypeLoaderBase.newInstance(SchemaTypeLoaderBase.java:198)位于org.apache.poi.xssf.usermodel.XSSFRichTextString。(XSSFRichTextString.java:87)的.CTRst $ Factory.newInstance(未知来源)org.apache.poi.xssf.usermodel.XSSFCell.setCellValue(XSSFCell.java:417 )ats.mongo.excelutil.OutputToExcelUtils.writeData(OutputToExcelUtils.java:80)at ups.mongo.excelutil.OutputToExcelUtils.clusterOutToExcel(OutputToExcelUtils.java:30)at ups.mongodb.App.main(App.java:74)

请给我一些建议吗?

谢谢你的尊重。

更新解决方案:使用XSSFWorkbook而不是XSSFWorkbook

public class OutputToExcelUtils {

    private static SXSSFWorkbook workbook;
    private static final String DATA_SEPERATOR = "!";

    public static void clusterOutToExcel(ClusterOutput clusterObject, ClusterOutputTrade clusterOutputTrade,
            ClusterOutputDistance ClusterOutputDistance, String outputPath) {

        workbook = new SXSSFWorkbook();
        workbook.setCompressTempFiles(true);
        FileOutputStream outputStream = null;

        writeData(clusterOutputTrade.getTrades(), "Data");

        try {
            outputStream = new FileOutputStream(outputPath);            
            workbook.write(outputStream);
            workbook.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void writeData(List<String> data, String sheetName) {

        int rowNum = 0;
        SXSSFSheet sheet = workbook.createSheet(sheetName);
        sheet.setRandomAccessWindowSize(100); // For 100 rows saved in memory, it will flushed after wirtten to excel file

        for (int i = 0; i < data.size(); i++) {
            System.out.println(sheetName + " Processing line: " + i);
            int colNum = 0;
            // Split into value of cell
            String[] valuesOfLine = data.get(i).split(DATA_SEPERATOR);

            Row row = sheet.createRow(rowNum++);

            for (String valueOfCell : valuesOfLine) {
                Cell cell = row.createCell(colNum++);
                cell.setCellValue(valueOfCell);
            }
        }
    }

}
答案

您的应用程序花费了太多时间进行垃圾回收。这并不一定意味着它的堆空间不足;但是,相对于执行实际工作,它在GC中花费的时间太多,因此Java运行时会将其关闭。

尝试使用以下JVM选项启用吞吐量收集:

-XX:+UseParallelGC

在您使用它的同时,为应用程序提供尽可能多的堆空间:

-Xms????m

(其中????代表以MB为单位的堆空间量,例如-Xms8192m

如果这没有帮助,请尝试使用此选项设置更宽松的吞吐量目标:

-XX:GCTimeRatio=19 

这指定您的应用程序应该比GC相关工作多做19倍的有用工作,即它允许GC消耗最多5%的处理器时间(我认为更严格的1%默认目标可能导致上述运行时错误)

不能保证他会工作。你可以检查并回复,以便遇到类似问题的其他人可能受益吗?

编辑

您的根本问题仍然是您需要在构建它时将整个spreadhseet及其所有相关对象保存在内存中。另一种解决方案是序列化数据,即编写实际的电子表格文件,而不是在内存中构建它并在最后保存它。但是,这需要读取XLXS格式并创建自定义解决方案。

另一个选择是寻找内存不足的库(如果存在)。 POI的可能替代方案是JExcelAPI(开源)和Aspose.Cells(商业)。

我在几年前就已经使用过JExcelAPI并且有一个积极的经验(但是,它似乎比POI更少维护,所以可能不再是最佳选择)。

编辑2

看起来POI提供了一个流媒体模型(https://poi.apache.org/spreadsheet/how-to.html#sxssf),所以这可能是最好的整体方法。

另一答案

好吧,尽量不要将所有数据加载到内存中。即使200k行的二进制表示不是那么大,内存中的隐藏对象也可能太大。正如你有一个Pojo的提示,这个pojo中的每个属性都有一个指针,每个指针取决于它是否被压缩或未被压缩将需要4或8个字节。这意味着如果您的数据是仅具有指针的4个属性的Pojo,您将花费200 000 * 4字节(或8字节)。

理论上你可以增加JVM的内存量,但这不是一个好的解决方案,或者更准确地说它不是Live系统的好解决方案。对于非交互式系统可能没问题。

提示:使用-Xmx -Xms jvm参数来控制堆大小。

另一答案

而不是从数据中获取整个列表,而是逐行迭代。如果过于繁琐,请将列表写入文件,然后按行重新读取,例如Stream<String>

  Path path = Files.createTempFile(...);
  Files.write(path, list, StandarCharsets.UTF_8);
  Files.lines(path, StandarCharsets.UTF_8)
     .forEach(line -> { ... });

在Excel方面:尽管xlsx使用共享字符串,但如果XSSF粗心地完成,则以下将使用单个String实例来重复字符串值。

public class StringCache {
    private static final int MAX_LENGTH = 40;
    private Map<String, String> identityMap = new Map<>();

    public String cached(String s) {
         if (s == null) {
             return null;
         }
         if (s.length() > MAX_LENGTH) {
             return s;
         }
         String t = identityMap.get(s);
         if (t == null) {
             t = s;
             identityMap.put(t, t);
         }
         return t;
    }
}

StringCache strings = new StringCache();

       for (String valueOfCell : valuesOfLine) {
            Cell cell = row.createCell(colNum++);
            cell.setCellValue(strings.cached(valueOfCell));
       }

以上是关于将大量数据写入excel:超出了GC开销限制的主要内容,如果未能解决你的问题,请参考以下文章

Spark DataFrame java.lang.OutOfMemoryError:长循环运行时超出了GC开销限制

Java jdbcpreparedStatement 超出了 OutOfMemoryError GC 开销限制

将大型数据集缓存到 spark 内存中时“超出 GC 开销限制”(通过 sparklyr 和 RStudio)

跟踪“超出 GC 开销限制”错误

优化 Hive 查询。 java.lang.OutOfMemoryError:超出 Java 堆空间/GC 开销限制

使用 R8 混淆应用程序会导致超出 GC 开销限制