You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:50:55