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

解决生成PDF时出现的“You did not close a PDF Document”警告及内存溢出问题

解决生成PDF时出现的“You did not close a PDF Document”警告及内存溢出问题

你遇到的这个警告和后续的内存溢出问题,根源是PDFBox底层的COSDocument实例没有被正确关闭,长期积累下来就会导致内存泄漏,最终触发OutOfMemoryError。咱们一步步来修复这个问题:

问题分析

你当前的代码虽然处理了输入流和输出流,但Flying Saucer在直接写入输出流的模式下,底层创建的PDF文档资源没有被显式释放,PDFBox的Finalizer线程会检测到未关闭的COSDocument并抛出警告。另外,每次调用方法都新建TemplateEngine也会额外消耗内存,加剧内存压力。

具体修复方案

1. 显式管理PDF文档资源,确保关闭

不要直接使用builder.toStream(),而是先构建出PDDocument实例,写入输出流后手动关闭它,这样就能彻底释放PDFBox的底层资源。

2. 复用TemplateEngine实例

把TemplateEngine作为Spring单例Bean注入(而不是每次调用方法都新建),避免重复创建对象带来的内存开销。

3. 移除不必要的Thread.sleep()

字体加载用try-with-resources已经能正确处理流,sleep完全没必要,只会拖慢接口响应。

修改后的完整代码

// 假设TemplateEngine已经作为Spring Bean注入进来
@Autowired
private TemplateEngine templateEngine;

private byte[] generateDocument(String language, String serviceType, String documentType) throws Exception {
    String templatePath = documentType + "/" + serviceType + "/" + language + ".html";
    
    if (!templateCheckerService.templateExists(templatePath)) {
        throw new RuntimeException("Template doesn't exist");
    }

    try {
        // 准备模板数据
        Map<String, Object> data = new HashMap<>();
        data.put("requestParameter", requestParameter);

        Context context = new Context();
        context.setVariables(data);
        // 加载图片Base64
        context.setVariable("logo", loadImageAsBase64("classpath:static/images/logo.png"));
        context.setVariable("cLogo", loadImageAsBase64("classpath:static/images/con.png"));

        // 处理模板生成HTML
        String htmlContent = templateEngine.process(loadHtmlTemplate("classpath:templates/" + templatePath), context);

        // 生成PDF并管理资源
        ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream();
        PdfRendererBuilder builder = new PdfRendererBuilder();
        builder.useFastMode();

        // 加载字体
        try (InputStream fontStream = resourceLoader.getResource("classpath:static/fonts/NotoSansMonoCJKjp-VF.ttf").getInputStream()) {
            builder.useFont(new FSSupplier<InputStream>() {
                @Override
                public InputStream supply() {
                    return fontStream;
                }
            }, "NotoSansMonoCJKjp-VF");

            builder.withHtmlContent(htmlContent, null);
            // 构建PDDocument而不是直接写入流
            PDDocument pdDocument = builder.buildPdf();
            try {
                // 将文档写入输出流
                pdDocument.save(pdfOutputStream);
            } finally {
                // 确保文档被关闭,释放底层资源
                pdDocument.close();
            }
        }

        return pdfOutputStream.toByteArray();
    } catch (Exception ex) {
        throw new Exception(ex);
    }
}

额外优化建议

  • 对于loadImageAsBase64方法,如果图片内容不经常变化,可以缓存Base64结果,避免重复读取和编码图片,进一步减少内存和IO开销。
  • 检查loadHtmlTemplate方法,确保模板资源被正确加载和关闭,避免潜在的资源泄漏。

备注:内容来源于stack exchange,提问作者user29640288

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 09:39:33