将大量数据写入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)