【问题标题】:java.lang.OutOfMemoryError when validating pdf with pdfbox preflight 2.0.13使用 pdfbox preflight 2.0.13 验证 pdf 时出现 java.lang.OutOfMemoryError
【发布时间】:2019-01-30 23:23:12
【问题描述】:

PDFBOX-4450 Details on Issue

不确定是否有人遇到过此问题,但在验证 pdf 时出现内存不足异常。在这里发帖以提高知名度,如果有人可以提供帮助,那就太棒了。

如果有人有任何想法,请分享。在这一点上,我真的无法前进。

我尝试过的东西

  • 遵循 wiki 中的建议但未成功 PDFBox faq

  • 最大堆大小从 2GB 增加到 4GB

  • 已删除 jvm arg:-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider

  • 尝试使用 jdk 1.7

  • 使用了临时文件(来自 wiki)
  • 为 PDImageXObject 禁用缓存(来自 wiki)

我的环境

  • Linux 64 位(arch linux)
  • Java 8
  • PDFBox/预检版本。 2.0.13
  • jbig imageio 版本。 3.0.2

Java 信息​​

java-版本

java 版本“1.8.0_131”

Java(TM) SE 运行时环境(内部版本 1.8.0_131-b11)

Java HotSpot(TM) 64 位服务器 VM(内部版本 25.131-b11,混合模式)

使用的 JVM 参数

java -Xmx2048m -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider

示例 pdf

Pdf from PDFBOX-4450

控制台输出

Jan 30, 2019 10:25:58 AM org.apache.pdfbox.pdmodel.font.PDType1Font <init>
WARNING: Using fallback font ArialMT for base font Symbol
Jan 30, 2019 10:25:58 AM org.apache.pdfbox.pdmodel.font.PDType1Font <init>
WARNING: Using fallback font ArialMT for base font ZapfDingbats
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1587)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1587)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1587)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1587)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1587)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.getDictionaryString(COSDictionary.java:1559)
at org.apache.pdfbox.cos.COSDictionary.toString(COSDictionary.java:1531)
at org.apache.pdfbox.preflight.xobject.XObjFormValidator.checkGroup(XObjFormValidator.java:138)
at org.apache.pdfbox.preflight.xobject.XObjFormValidator.validate(XObjFormValidator.java:73)
at org.apache.pdfbox.preflight.process.reflect.GraphicObjectPageValidationProcess.validate(GraphicObjectPageValidationProcess.java:74)
at org.apache.pdfbox.preflight.utils.ContextHelper.callValidation(ContextHelper.java:84)
at org.apache.pdfbox.preflight.utils.ContextHelper.validateElement(ContextHelper.java:57)
at org.apache.pdfbox.preflight.process.reflect.ResourcesValidationProcess.validateXObjects(ResourcesValidationProcess.java:224)
at org.apache.pdfbox.preflight.process.reflect.ResourcesValidationProcess.validate(ResourcesValidationProcess.java:81)
at org.apache.pdfbox.preflight.utils.ContextHelper.callValidation(ContextHelper.java:84)

示例代码

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.preflight.PreflightDocument;
import org.apache.pdfbox.preflight.ValidationResult;
import org.apache.pdfbox.preflight.ValidationResult.ValidationError;
import org.apache.pdfbox.preflight.parser.PreflightParser;

public class Validator {
  private File file = null;
  private List<ValidationError> errorList = new ArrayList<ValidationError>();

  public Validator(File file) {
    this.file = file;
  }

  public List<ValidationError> getErrors(){
    return errorList;
  }

  public boolean validate() throws Exception{
    PreflightParser parser = null;
    PreflightDocument document = null;
    ValidationResult result = null;
    try {
      parser = new PreflightParser(file);
      parser.parse();
      document = parser.getPreflightDocument();
      document.validate();
      result = document.getResult();
      errorList = result.getErrorsList();
    }
    catch(Exception e) {
      throw e;
    }
    finally {
      if(document != null) {
        try {
          document.close();
        }catch(Exception ignored) {}
      }
      parser = null;
      document = null;
      result = null;
    }
    return errorList.size() > 0 ? true : false;
  }
}

【问题讨论】:

  • 您也可以尝试这里提到的选项:issues.apache.org/jira/browse/PDFBOX-4384`document.getContext().getConfig().setMaxErrors(xxx)` 我会在接下来的几天内查看您的文件,但我也需要睡觉和做我的日常工作。
  • “在这一点上,我真的无法前进” - 你是怎么卡住的?该文件显然不是 PDF/A。
  • 这不是真正的建设性,我的人,它被卡住了,因为我无法使用预检来验证而不抛出内存不足错误。 PDFBOX-4384 看起来很有希望。

标签: java apache out-of-memory pdfbox


【解决方案1】:

当我添加这些选项时:

-XX:+HeapDumpOnOutOfMemoryError -Xmx3550m -Xms3550m -Xmn2g 

又失败了。我使用 VisualVM 来分析转储堆文件。我发现了一些有趣的东西。

而char[]的大部分内容是:

我在

中找到了代码
//org.apache.pdfbox.preflight.process.reflect.SinglePageValidationProcess#validateGroupTransparency
    protected void validateGroupTransparency(PreflightContext context, PDPage page) throws ValidationException
    {
        COSBase baseGroup = page.getCOSObject().getItem(XOBJECT_DICTIONARY_KEY_GROUP);
        COSDictionary groupDictionary = COSUtils.getAsDictionary(baseGroup, context.getDocument().getDocument());
        if (groupDictionary != null)
        {
            String sVal = groupDictionary.getNameAsString(COSName.S);
            if (XOBJECT_DICTIONARY_VALUE_S_TRANSPARENCY.equals(sVal))
            {
                context.addValidationError(new ValidationError(ERROR_GRAPHIC_TRANSPARENCY_GROUP,
                        "Group has a transparency S entry or the S entry is null"));
            }
        }
    }

它创建了一个ValidationError对象,但构造函数是:

public ValidationError(String errorCode, String details, Throwable cause)
        {
            this(errorCode);
            if (details != null)
            {
                StringBuilder sb = new StringBuilder(this.details.length() + details.length() + 2);
                sb.append(this.details).append(", ").append(details);
                this.details = sb.toString();
            }
            this.cause = cause;
            t = new Exception();
        }

可以看到,一旦出现错误,它会创建ValidationError并创建一个StringBuilder。

那么,你有三种方法可以解决这个问题:

  1. 您可以扩展堆大小。 4G不够用,试试16G以上吧。
  2. 不要使用 PDFBox 库。
  3. 更改 PDFBox 源代码。
    public ValidationError(String errorCode, String details, Throwable cause)
    {
        this(errorCode);
        if (details != null)
        {
            String key = errorCode + details;
            if (commonDetailMap.containsKey(key)) {
                this.details = commonDetailMap.get(key);
            } else {
                StringBuilder sb = new StringBuilder(this.details.length() + details.length() + 2);
                sb.append(this.details).append(", ").append(details);
                this.details = sb.toString();
                commonDetailMap.put(key, this.details);
            }

        }
        this.cause = cause;
        t = new Exception();
    }

我认为使用 Map 来避免创建 StringBuilder 也可能会起作用。但是如果错误代码和详细信息是多值的,则地图会太大。

所以,另一种更改源代码的方法是:

    public ValidationError(String errorCode, String details, Throwable cause)
    {
        this(errorCode);
        if (details != null)
        {
            StringBuilder sb = new StringBuilder(this.details.length() + details.length() + 2);
            sb.append(this.details).append(", ").append(details);
            // invoke intern
            this.details = sb.toString().intern();
        }
        this.cause = cause;
        t = new Exception();
    }

intern() 是:

Returns a canonical representation for the string object.

我认为使用 intern() 更好。

【讨论】:

  • 我最终所做的只是将预检代码(只是硬编码)中抛出的 ValidationError 的数量限制为一个非常小的数字。我的用例实际上只是试图找出给定的 pdf 是否通过在进一步处理之前验证与否。这种方法 (intern()) 似乎可以正常工作,但如果您有大量异常,则需要一些时间。感谢所有反馈,非常感谢!
  • This Question可以给你一些建议
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-12-04
  • 2020-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-09
相关资源
最近更新 更多