【问题标题】:Memory leaks/spike while parsing pdf images to be sent to ftp解析要发送到 ftp 的 pdf 图像时内存泄漏/峰值
【发布时间】:2020-10-19 20:33:12
【问题描述】:

我有一个 20MB 和 9 页的 PDF 文档,我正在尝试将其转换为图像并将它们 FTP,我正在使用 pdfBox(2.0.16 版本)并且以下逻辑导致堆内存从小于 500 开始出现严重峰值MB一直到2000MB。我遵循了 pdf 框 (https://pdfbox.apache.org/2.0/faq.html#outofmemoryrrror) 的常见问题解答,但到目前为止它没有帮助。我从当前的 2.0.16 版本升级到 2.0.20,但这也没有解决问题。任何有关错误的指示都将对我解决此问题非常有帮助。 20 MB 文件的 1500 MB 内存增加很多,我正在通过 tomcat 运行我的应用程序,它使服务器崩溃。

P.S:如果有任何其他我可以做的事情或更多细节,请告诉我。

代码:

                PDDocument document = PDDocument.load(sourceFile,
                    MemoryUsageSetting.setupTempFileOnly());
                PDFRenderer renderer = new PDFRenderer(document);
                PDPageTree pdPageTree =  document.getDocumentCatalog().getPages();
                int pageNumber = 1;
                strPdfFileName = strPdfFileName.replace(" ", "_"); 
                UploadFileBuilder objUploadFileBuilder;
                BufferedImage image;
                File outputFile;
                for (int i = 0; i < pdPageTree.getCount(); i++) {
                    strUploadFileName = new StringBuilder(strPdfFileName);
                    strUploadFileName.append("_").append(pageNumber).append(".png");
                    image = renderer.renderImage(pageNumber-1);
                    outputFile = new File(strPdfFilePath + File.separator + strPdfFileName +"_"+ pageNumber +".png");
                    LOGGER.debug("Image Created ----> {}", outputFile.getName());
                    ImageIO.write(image, "png", outputFile);
                    image.flush();
                    objUploadFileBuilder=new UploadFileBuilder();
                    objUploadFileBuilder.on(strUploadFileName.toString()).path(strFtpDir.toString()).withData(outputFile.getAbsolutePath());
                    lstUploadFileBuilder.add(objUploadFileBuilder);
                    strFileNameList.add(strUploadFileName.toString());
                    pageNumber++;
                }


                document.close();
                UploadFileBuilder.execute(lstUploadFileBuilder);

Edit1:问题的堆栈跟踪,还进行了更改以刷新图像。

Edit2:当前 -XMX 设置(JVM 的最大内存分配池为 512m),我使用的是 java 1.8.0_91

【问题讨论】:

  • 可以分享PDF吗?您可以尝试的另一件事是调用 renderer.setSubsamplingAllowed(true)。
  • @TilmanHausherr 抱歉它的 20MB,我无法上传,但这是我从开放互联网 hq.nasa.gov/alsj/a17/A17_FlightPlan.pdf 测试的 PDF 之一,我会尝试您建议的选项。
  • @TilmanHausherr 在尝试该选项后,我仍然看到内存峰值。我无法上传,但这是我从开放互联网 hq.nasa.gov/alsj/a17/A17_FlightPlan.pdf 测试的 PDF 之一
  • 我能够在 jdk1.8.0_252 (amazon corretto) 上使用 -Xmx1g 和该文件运行 PDFToImage。确保您使用的是最新的 jdk,尤其是在使用 jdk8 时。 jdk11 和 14 使用的内存也更少。完成图像后也尝试添加“image.dispose()”。
  • 是的,这是一个问题。错误得到修复(这包括内存泄漏),安全漏洞得到修复。并确保 tomcat 也已更新(如果适用)。以下是您不更新软件时发生的情况的示例:en.wikipedia.org/wiki/2017_Equifax_data_breach

标签: tomcat memory-leaks out-of-memory pdfbox heap-memory


【解决方案1】:

您可以尝试以下方法:

(1) 将以下依赖添加到你的maven项目中(如果你不使用maven,也可以手动添加)

<dependency>
    <groupId>net.gredler</groupId>
    <artifactId>jdk9-png-writer-backport</artifactId>
    <version>1.0.0</version>
</dependency>

(2) 更改您的转换代码以使用 backportedWriter 和 FileCacheImageOutputStream:

private static void convertMethod2(File pdf) {
        try (final PDDocument document = PDDocument.load(pdf)) {
            PDFRenderer pdfRenderer = new PDFRenderer(document);
            List<String> base64DocumentPages = new ArrayList<>();               

            for (int page = 0; page < document.getNumberOfPages(); ++page) {
                BufferedImage bim = pdfRenderer.renderImage(page);
                
                FileOutputStream baos = new FileOutputStream(strPdfFilePath + File.separator + strPdfFileName +"_"+ pageNumber +".png"););
                PNGImageWriterBackport writer = chosePngWriter();
            
                if(writer!=null) {
                    try (ImageOutputStream stream = new FileCacheImageOutputStream(baos,null)) {
                        writer.setOutput(stream);
                        writer.write(null,new IIOImage(bim, null, null), getImageParams(writer));
                    }
                    finally {
                        writer.dispose();
                    }
                }
                else {
                    System.err.println("PNGImageWriterBackport not found! Aborting");
                }
                //do what you need to do
            }
            document.close();
        }
        catch (IOException e) {
            //handle exception
        }
    }
    
    private static PNGImageWriterBackport chosePngWriter() {
        Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByFormatName("png");
        
        ImageWriter writer = null;
        while(imageWriters.hasNext()) {
        
            writer = imageWriters.next();
            if (writer instanceof PNGImageWriterBackport) {
                return (PNGImageWriterBackport)writer;
            }
        }
        return null;
    }
    
    private static ImageWriteParam getImageParams(PNGImageWriterBackport writer) {
        ImageWriteParam writeParam = writer.getDefaultWriteParam();
        //set compression mode which wasn't possible before
        writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        //0.0f highest compression, slowest
        //1.0f lowest compression, fastest
        writeParam.setCompressionQuality(0.86f);
        
        return writeParam;
    }

【讨论】:

  • 我尝试了这个选项,从视觉 vm 我仍然看到我从 300 MB 到大约 1500 MB 的原始峰值。我对 xmlx 有一个 512 mb 的硬性限制,并且只有大约 50 mb 的文件,它使堆的峰值如此之大,以至于我的 tomcat 崩溃了。
猜你喜欢
  • 2013-05-10
  • 1970-01-01
  • 1970-01-01
  • 2016-12-02
  • 1970-01-01
  • 1970-01-01
  • 2017-03-30
  • 2011-10-18
  • 1970-01-01
相关资源
最近更新 更多