Docx转PDF输出至ServletResponse时OOM问题的解决咨询
Docx转PDF时OOM问题的解决方案及替代库
问题根源
从堆栈日志可以看出,OOM出现在iText 2.1.7处理PDF流的过程中——这个旧版本会用ByteArrayOutputStream在内存中缓存大量转换中间数据,哪怕原Docx只有500KB,复杂的文档结构(比如嵌套表格、大量格式元素)会导致内存占用急剧膨胀,单纯调大JVM堆内存无法从根本上解决问题。
现有代码的修复方案
1. 缓存字体实例,避免重复加载
当前代码每次调用字体提供者都会重新创建BaseFont,重复加载字体文件会浪费大量内存,改成类级单例缓存:
// 类级别缓存字体实例 private static BaseFont cachedTimesNewRoman; static { try { cachedTimesNewRoman = BaseFont.createFont( "classpath:fonts/Times_New_Roman.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED ); } catch (Exception e) { log.error("Failed to load Times New Roman font", e); } } // 使用缓存的字体实例 options.fontProvider((familyName, encoding, size, style, color) -> { if (cachedTimesNewRoman == null) { return null; } Font font = new Font(cachedTimesNewRoman, size, style, color); if (familyName != null) { font.setFamily(familyName); } return font; });
2. 用临时文件做中间缓存,减少内存占用
避免直接将转换结果写入ServletResponse的输出流,先写入临时文件,再将临时文件流输出到响应,避免内存中缓存完整的PDF数据:
XWPFDocument doc = new XWPFDocument(inputStream); PdfOptions options = PdfOptions.create(); // 配置options... // 创建临时文件存储转换后的PDF File tempPdf = File.createTempFile("docx-convert-", ".pdf"); try (FileOutputStream tempOut = new FileOutputStream(tempPdf)) { PdfConverter.getInstance().convert(doc, tempOut, options); } // 将临时文件输出到响应 try (FileInputStream tempIn = new FileInputStream(tempPdf)) { IOUtils.copy(tempIn, response.getOutputStream()); response.getOutputStream().flush(); } finally { // 清理临时文件 tempPdf.deleteOnExit(); doc.close(); }
3. 升级xdocreport依赖版本
当前使用的xdocreport 2.1.0依赖老旧的iText 2.1.7,升级到最新稳定版(如2.1.2),新版本会优化内存管理,部分版本还会替换为更高效的PDF处理库。
替代库推荐
1. Apache PDFBox + Apache POI
完全开源,可控性强,可手动控制转换过程中的内存占用,适合对内存敏感的场景。大致实现逻辑:
XWPFDocument doc = new XWPFDocument(inputStream); PDDocument pdfDoc = new PDDocument(); PDPage page = new PDPage(); pdfDoc.addPage(page); PDPageContentStream contentStream = new PDPageContentStream(pdfDoc, page); // 遍历Docx的段落、表格等元素,逐个写入PDF // 需自行处理字体、样式、布局,代码量较大但内存可控 contentStream.close(); pdfDoc.save(response.getOutputStream()); pdfDoc.close(); doc.close();
2. Aspose.Words for Java
商业库,转换质量高,对复杂Docx支持好,内存优化到位,无需手动处理样式细节,但需要购买授权。
3. LibreOffice命令行转换
通过调用LibreOffice的无头模式命令行工具完成转换,完全脱离Java内存限制,适合超复杂文档:
libreoffice --headless --convert-to pdf /path/to/input.docx --outdir /tmp
Java中可通过ProcessBuilder调用该命令,读取生成的PDF文件后输出到响应。
docx4j依赖冲突解决技巧
如果仍想使用docx4j,针对Java 11+的javax bind冲突,可通过排除旧依赖、引入Jakarta替代包解决:
<dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-core</artifactId> <version>8.3.9</version> <exclusions> <exclusion> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>jakarta.xml.bind</groupId> <artifactId>jakarta.xml.bind-api</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>4.0.2</version> </dependency>
内容的提问来源于stack exchange,提问作者Meetra
相关产品推荐
相关产品推荐

