签署PDF - 内存消耗

时间:2015-05-26 04:15:12

标签: itext itextpdf

我尝试了一些基于iText v1或v2的数字PDF签名实用程序,发现似乎整个PDF被加载到内存中(对于60M PDF进程可能需要300-400MB的内存)。

最近的iText版本可以在不加载内存的情况下签署PDF吗?

更新

我使用itextpdf 5.5.6测试了Bruno的例子

  • PdfReader构造函数并不重要 - 它可以是(src)或(src,null,true),或者 (src,null,false) - 结果相同。
  • 重要的是createSignature中的新文件(tmp)。

但内存消耗仍然很大。我试着签署100M文件(带有嵌入式附件的PDF),峰值内存大约是325M。当然,它比没有临时文件的540M好,但还不够好((。(

最大32K文件内存是65M(我想是JVM和java代码本身)

使用/usr/bin/time -v java ....

测量内存

我使用-Xmx100m来限制Java内存,但是因为内存不足而崩溃了:

  

线程中的异常" main" java.lang.OutOfMemoryError:Java堆空间

     

at com.itextpdf.text.pdf.PdfReader.getStreamBytesRaw(PdfReader.java:2576)       at com.itextpdf.text.pdf.PdfReader.getStreamBytesRaw(PdfReader.java:2615)       at com.itextpdf.text.pdf.PRStream.toPdf(PRStream.java:230)       at com.itextpdf.text.pdf.PdfIndirectObject.writeTo(PdfIndirectObject.java:158)       at com.itextpdf.text.pdf.PdfWriter $ PdfBody.write(PdfWriter.java:420)       at com.itextpdf.text.pdf.PdfWriter $ PdfBody.add(PdfWriter.java:398)       at com.itextpdf.text.pdf.PdfWriter.addToBody(PdfWriter.java:887)       at com.itextpdf.text.pdf.PdfStamperImp.close(PdfStamperImp.java:412)       at com.itextpdf.text.pdf.PdfStamperImp.close(PdfStamperImp.java:386)       at com.itextpdf.text.pdf.PdfSignatureAppearance.preClose(PdfSignatureAppearance.java:1316)       at com.itextpdf.text.pdf.security.MakeSignature.signDetached(MakeSignature.java:140)

代码是:

public static byte[] getStreamBytesRaw(final PRStream stream, final RandomAccessFileOrArray file) throws IOException {
        PdfReader reader = stream.getReader();
        byte b[];
        if (stream.getOffset() < 0)
            b = stream.getBytes();
        else {
      ----> b = new byte[stream.getLength()];
            file.readFully(b);

我在调试器中看到流类型是EmbeddedFile,长度是100M - 因此整个嵌入式文件正被读入内存。

更新 - 创建大PDF

分享100M文件很难)),但这里是创建序列:

  1. 运行dd if=/dev/urandom of=file.bin bs=1048000 count=100
  2. 转到http://blog.didierstevens.com/programs/pdf-tools/并点击http://didierstevens.com/files/software/make-pdf_V0_1_6.zip
  3. 解压缩并运行python make-pdf-embedded.py file.bin file.pdf
  4. 你在这里)

    我应该注意使用 / dev / urandom 非常重要。 / dev / zero 创建的压缩PDF只有100K大小。

    无论如何,如果需要获取我的文件,我已在服务器上创建了50M文件 - http://50mpdf.tk/50m.pdf

2 个答案:

答案 0 :(得分:2)

签署PDF时,iText使用相关的内存量

  • 将整个未签名的PDF读入内存,除非在部分模式下使用PdfReader ;
  • 在内存中创建签名文件,除非使用配置为使用临时文件的PdfStamper;和
  • 将未签名数据复制到待签名文件时,将整个单独的PDF对象(例如包含嵌入文件的流)读入内存,除非在追加模式下使用PdfStamper 。 / LI>

E.g。签署样本50提供的文件需要

  • 关于-Xmx240m如果既不使用追加模式,也不使用临时文件,也不使用部分模式;
  • 关于-Xmx81m如果使用临时文件但不是附加模式,则部分模式没有区别;
  • 关于-Xmx7m如果使用追加模式和临时文件,则部分模式没有区别。

部分模式在后面的情况下没有区别的原因是,即使在非部分模式下,PdfReader在初始化期间似乎也不会读取流内容。由于示例文件主要由单个大流的内容组成,因此在初始化期间读取或未读取的少数对象不会产生影响,尤其是即使在部分模式下PdfReader读取并保留一些对象在内存中反映全球文档结构,例如页面树。

您可以在此处找到我的测试程序:CreateSignature.java。我使用iText 5.5.7-SNAPSHOT在64位MS Windows Java 8上运行它(在这种情况下不应该与5.5.6版本不同)。

因此,对于内存友好的签名,请使用@ Bruno代码的变体:

// Creating the reader and the stamper
PdfReader reader = new PdfReader(filepath, null, true);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper =
    PdfStamper.createSignature(reader, os, '\0', new File(tmp), true);
// Creating the appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
// Creating the signature
ExternalSignature pks = new PrivateKeySignature(pk, digestAlgorithm, provider);
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, pks, chain,
    null, null, null, 0, subfilter);

答案 1 :(得分:1)

请下载免费的电子书Digital Signatures for PDF documents。第2.2.4节标题为#34;签署大型PDF文件&#34;。它解释了如何使用临时文件签署文档,而不是将文件保存在内存中:

// Creating the reader and the stamper
PdfReader reader = new PdfReader(filepath, null, true);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper =
    PdfStamper.createSignature(reader, os, '\0', new File(tmp));
// Creating the appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
// Creating the signature
ExternalSignature pks = new PrivateKeySignature(pk, digestAlgorithm, provider);
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, pks, chain,
    null, null, null, 0, subfilter);

您是否了解我们如何创建PdfStamper实例?我们将File对象添加为createSignature()方法的额外参数。此代码示例中的tmp变量可以是特定文件或目录的路径。如果选择了目录,iText将在该目录中创建一个具有唯一名称的文件。

如果您将createSignature()方法与临时文件一起使用,则可以使用OutputStreamos值)null。在这种情况下,临时文件将用作实际目标文件。如果您的目标是在文件系统上存储已签名的文件,这是一种很好的做法。如果OutputStream不是null,则在签名完成后,iText将始终尝试删除临时文件。

请不要再使用iText v1或v2。使用这些版本创建的签名类型已过时,iText版本也是如此(另请参阅https://stackoverflow.com/questions/25696851/can-itext-2-1-7-or-earlier-can-be-used-commercially)。