Java 8+POI写入Excel时频繁JVM崩溃,求代码级解决方案
Apache POI 3.12 + Java 8 写入Excel时JVM崩溃(libzip.so相关)
问题详情
使用Apache POI 3.12、Java 8及Tomcat服务器写入Excel文件时,频繁触发JVM崩溃,生成的hs_err_pid错误日志如下:
# # A fatal error has been detected by the Java Runtime Environment: # # SIGBUS (0x7) at pc=0x00007f16e02d1ec0, pid=1135, tid=0x00007f1595ef8700 # # JRE version: OpenJDK Runtime Environment (8.0_352-b08) (build 1.8.0_352-8u352-ga-1~18.04-b08) # Java VM: OpenJDK 64-Bit Server VM (25.352-b08 mixed mode linux-amd64 compressed oops) # Problematic frame: # C [libzip.so+0x4ec0] # # Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again # # If you would like to submit a bug report, please visit: # http://bugreport.java.com/bugreport/crash.jsp # The crash happened outside the Java Virtual Machine in native code. # See problematic frame for where to report the bug. ------ Stack: [0x00007f1595df8000,0x00007f1595ef9000], sp=0x00007f1595ef5900, free space=1014k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) C [libzip.so+0x4ec0] C [libzip.so+0x5b6e] C [libzip.so+0x3b25] Java_java_util_zip_ZipFile_getEntry+0x85 J 91 java.util.zip.ZipFile.getEntry(J[BZ)J (0 bytes) @ 0x00007f16cd13ea8e [0x00007f16cd13e9c0+0xce] J 123871 C2 java.util.zip.ZipFile.getInputStream(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream; (304 bytes) @ 0x00007f16d6716944 [0x00007f16d67165c0+0x384] J 124037 C2 org.apache.poi.openxml4j.opc.PackagePart.getInputStream()Ljava/io/InputStream; (44 bytes) @ 0x00007f16cda4bd00 [0x00007f16cda4bc60+0xa0] J 103326 C1 org.apache.poi.POIXMLProperties.<init>(Lorg/apache/poi/openxml4j/opc/OPCPackage;)V (212 bytes) @ 0x00007f16d6af7374 [0x00007f16d6af6300+0x1074] J 100022 C1 org.apache.poi.POIXMLDocument.getProperties()Lorg/apache/poi/POIXMLProperties; (40 bytes) @ 0x00007f16cd79ddfc [0x00007f16cd79dce0+0x11c] J 67174 C1 org.apache.poi.POIXMLDocument.write(Ljava/io/OutputStream;)V (35 bytes) @ 0x00007f16cd89404c [0x00007f16cd893a80+0x5cc] J 65014 C1 com.bulk.helper.ExcelUtility.saveWorkBook(Lorg/apache/poi/ss/usermodel/Workbook;Ljava/lang/String;Ljava/lang/String;)V (89 bytes) @ 0x00007f16cd543754 [0x00007f16cd542ea0+0x8b4] J 71211 C1 com.bulk.helper.ExcelAccess.save_userfile(Ljava/lang/String;Ljava/lang/String;)V (14 bytes) @ 0x00007f16ce6e46ec [0x00007f16ce6e4640+0xac] J 93237 C1
相关业务代码:
// 调用类 accessFile.save_newFile(---); // 通用工具类 @Override public void save_userfile(String filePath , String fileName) { utility.saveWorkBook(userWorkbook,filePath,fileName); } public void saveWorkBook(Workbook workbook ,String filePath, String fileName){ try { File directory = new File(filePath); if (!directory.exists()) { directory.mkdir(); } File file = new File(directory,fileName); OutputStream out = new FileOutputStream(file); workbook.write(out); out.flush(); out.close(); workbook.close(); }catch(Exception e){ LOG.error(Constants.ERROR_STRING,e); } }
已知添加-Dsun.zip.disableMemoryMapping=true参数可解决,但会增加磁盘IO影响性能,希望通过Java 8代码层面直接修复。
代码层面解决方案
1. 规避POIXMLProperties读取的Zip内存映射问题
崩溃栈显示问题出在POIXMLProperties初始化时调用ZipFile的native方法,这是因为Apache POI 3.12处理OOXML文档时,默认使用Java ZipFile的内存映射机制,而Java 8的libzip.so存在相关bug。可通过修改写入逻辑跳过不必要的属性读取:
public void saveWorkBook(Workbook workbook ,String filePath, String fileName){ try { File directory = new File(filePath); if (!directory.exists()) { directory.mkdirs(); // 替换mkdir为mkdirs,支持多级目录创建 } File file = new File(directory,fileName); // 针对XSSFWorkbook(OOXML格式)处理属性问题 if (workbook instanceof XSSFWorkbook) { XSSFWorkbook xssfWorkbook = (XSSFWorkbook) workbook; OPCPackage pkg = xssfWorkbook.getPackage(); // 清空核心属性,避免触发ZipFile内存映射读取 pkg.getPackageProperties().setCreatorProperty(null); pkg.getPackageProperties().setLastModifiedByProperty(null); // 设置为只读模式,跳过属性写入流程 pkg.setReadOnly(true); } // 使用try-with-resources自动关闭流,避免资源泄漏 try (OutputStream out = new FileOutputStream(file)) { workbook.write(out); } finally { workbook.close(); } }catch(Exception e){ LOG.error(Constants.ERROR_STRING,e); } }
2. 替换Java原生Zip实现为Apache Commons Compress
使用Apache Commons Compress的ZipArchive系列类替代Java原生Zip实现,避开libzip.so的bug。需先引入依赖:
<!-- Maven依赖,选择兼容Java 8的版本 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.21</version> </dependency>
自定义XSSFWorkbook写入逻辑:
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.io.IOUtils; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.openxml4j.opc.PackagePart; public void saveXssfWorkbook(XSSFWorkbook workbook, File file) throws IOException { try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(file)) { zos.setUseZip64(ZipArchiveOutputStream.Zip64Mode.AsNeeded); // 遍历Workbook所有部件,写入到Zip包 for (PackagePart part : workbook.getPackage().getParts()) { ZipArchiveEntry entry = new ZipArchiveEntry(part.getPartName().getName()); entry.setSize(part.getSize()); zos.putArchiveEntry(entry); try (InputStream is = part.getInputStream()) { IOUtils.copy(is, zos); } zos.closeArchiveEntry(); } } workbook.close(); }
3. 升级Apache POI版本(推荐)
Apache POI 3.12是2015年的老旧版本,后续版本(如3.17+、4.1.2)已针对Java 8的ZipFile问题做了修复,升级到兼容Java 8的最新稳定版本,可从根源解决问题,同时获得更多bug修复和性能优化。
内容的提问来源于stack exchange,提问作者JAK
相关产品推荐
相关产品推荐

