Java 使用 IText 7 签署 Pdf 时堆 Space

Java Heap Space when Sign a Pdf with IText 7

我创建了这个方法来签署 pdf 文件:

public File sign7(File pdfOriginal,
                          String diretorioSalvar,
                          String nomeArquivo,
                          String motivo, String local,
                          LocalDateTime data,
                          String textoAssinatura,
                          boolean visible,
                          PDFService.DisposicaoPagina dispPagina,
                          File arquivoOriginal
                          ) throws IOException, DocumentException, GeneralSecurityException {

            log.debug("comecou assinar");
            File diretorioSaida = new File(diretorioSalvar);
            diretorioSaida.mkdirs();
            File pdfAssinado = new File(diretorioSalvar+File.separator+nomeArquivo);
            String keystore_password = KEYSTORE_PASSWORD;
            String key_password = KEYSTORE_PASSWORD;
            keystore.load(KEYSTORE.getInputStream(), keystore_password.toCharArray());

            PrivateKey key = (PrivateKey) keystore.getKey(alias, key_password.toCharArray());
            Certificate[] chain = keystore.getCertificateChain(alias);

            log.debug("keystore provider   : {}", keystore.getProvider().getName());
            log.debug("Assinando com alias :{}", alias);
            log.debug("chain size: " + chain.length);

            PdfReader reader = new PdfReader(new RandomAccessBufferedFileInputStream(pdfOriginal));
            PdfWriter writer = new PdfWriter(pdfAssinado);
            PdfDocument doc = new PdfDocument(reader,writer);
            FileOutputStream os = new FileOutputStream(pdfAssinado);

            PdfSigner signer = new PdfSigner(doc.getReader(),os,true);
            signer.setCertificationLevel(PdfSigner.NOT_CERTIFIED);


            //TEXTO DO CARIMBO
            String texto;
            ImageData imgCarimbo;


            PdfPage moldPage = doc.getLastPage();
            PageSize pSize = new PageSize(moldPage.getPageSize());
            PdfCanvas cPage = new PdfCanvas(moldPage);


            PdfFont font = null;
            try {
                font = PdfFontFactory.createFont(FONT.getFile().getPath(), PdfEncodings.WINANSI, true);
            } catch (IOException e) {
                e.printStackTrace();
            }
            cPage.setFillColor(Color.BLACK);

            Rectangle rect = new Rectangle(
                    (float) (pSize.getWidth()*0.653),  //0.725  Y
                    (float) (pSize.getHeight()*0.9),  //0.90    X
                    (float) (pSize.getWidth()*0.32),   //0.25   Largura
                    (float) (pSize.getHeight()*0.068)); //0.07   Altura



            cPage.fillStroke();


            PdfFormXObject xObject = new PdfFormXObject(rect);

            Image rectImg = new Image(xObject);

            ImageData imgLogoCarimboOval = ImageDataFactory.create(LOGO_CARIMBO_DIGITAL.getFile().getPath());
            ImageData imgLogoCarimboBg = ImageDataFactory.create(LOGO_CARIMBO_BG.getFile().getPath());


            int paginaAparencia = (dispPagina == PDFService.DisposicaoPagina.ULTIMA_PAGINA?doc.getNumberOfPages():1);

            String arqOriginalHash = "";
            if (arquivoOriginal != null) {
                arqOriginalHash = pdfService.gerarHash(arquivoOriginal);
            }

            PdfSignatureAppearance appearance = signer
                    .getSignatureAppearance()
                    .setReason(motivo + " - Hash: " + arqOriginalHash)
                    .setLocation(local)
                    .setReuseAppearance(false)
                    .setImage(imgLogoCarimboBg)
                    .setSignatureGraphic(imgLogoCarimboOval)
                    .setImageScale(100)
                    .setRenderingMode(RenderingMode.GRAPHIC_AND_DESCRIPTION)
                    .setPageRect(rect)
                    .setLayer2Font(font)
                    .setLayer2FontSize(6)
                    .setLayer2Text(textoAssinatura)
                    .setPageNumber(paginaAparencia);



            signer.setFieldName(signer.getNewSigFieldName());
            // Creating the signature
            IExternalSignature pks = new PrivateKeySignature(key, DigestAlgorithms.SHA1, "BC");
            IExternalDigest digest = new ProviderDigest("BC");


            Collection<ICrlClient> crlList=null; IOcspClient ocspClient = null; ITSAClient tsaClient=null;

            writer.close();
            signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
            reader.close();

            os.close();
            log.debug("acabou assinar");
            return pdfAssinado;
    }    

(此方法在我的最后一页创建图章并签署 pdf)但是当我尝试签署一个 500MB 的文件时,我得到了一个 java 堆 Space 行:

signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);

如果我尝试对较小的文件进行签名(我只一个一个地尝试过,不知道我是否同时尝试多个我会得到同样的错误)

我已经尝试通过我的应用更改内存,但没有成功。

PdfReader实例化

在您的 PdfReader 实例化中,您混淆了 PDF 库和内存中原始文件副本的额外内存需求加上一点。对于 File pdfOriginal 你做:

PdfReader reader = new PdfReader(new RandomAccessBufferedFileInputStream(pdfOriginal));

RandomAccessBufferedFileInputStream 不是 iText class!但是有一个 PDFBox class 这个名字,我假设你在这里使用。此 PDFBox class 是一个 InputStream,它还实现了 PDFBox 的 RandomAccessRead 接口,以通用随机访问方式处理本地文件系统文件。

由于 iText 确实有自己的机制来实现文件随机访问,特别是不为此使用 PDFBox 接口,它只识别并使用 RandomAccessBufferedFileInputStream 实例作为 InputStream。因此,iText 将该 Stream 中的所有数据读入 byte[] 以获得适当的随机访问支持。

相反,如果您允许 iText 看到源是本地文件系统文件,它可以使用自己的随机文件访问权限,并且不会创建文件的内存副本。只需使用

PdfReader reader = new PdfReader(pdfOriginal);

对于您的 500MB 文件,这将减少 500 MB 的内存使用量。

额外 PdfWriterPdfDocument 个实例

此外,你做到了

PdfWriter writer = new PdfWriter(pdfAssinado);
PdfDocument doc = new PdfDocument(reader,writer);
FileOutputStream os = new FileOutputStream(pdfAssinado);

PdfSigner signer = new PdfSigner(doc.getReader(),os,true);

即您自己创建了一个 PdfWriter 和一个 PdfDocument 实例,它们只会占用额外的内存,然后创建一个具有自己内部 PdfWriterPdfDocument 实例的 PdfSigner

因此,不要创建您自己的 PdfWriterPdfDocument 实例。您稍后访问自己的 PdfDocument 实例以确定页面大小;你应该改用 PdfSigner

FileOutputStream os = new FileOutputStream(pdfAssinado);
PdfSigner signer = new PdfSigner(reader,os,true);
PdfDocument doc = signer.getDocument();

并删除后面的writer.close()指令。

无论这些额外对象需要多少,这都会减少内存使用量。

内存中的临时文件副本

你像这样实例化 PdfSigner

PdfSigner signer = new PdfSigner(reader,os,true);

如 JavaDocs 中所述,此构造函数将中间文件副本(签名创建期间需要)保存在 ByteArrayOutputStream 实例中,即在内存中:

/**
 * Creates a PdfSigner instance. Uses a {@link java.io.ByteArrayOutputStream} instead of a temporary file.
 *
 * @param reader       PdfReader that reads the PDF file
 * @param outputStream OutputStream to write the signed PDF file
 * @param append       boolean to indicate whether the signing should happen in append mode or not
 * @throws IOException
 * @deprecated         will be removed in next major release.
 *                     Use {@link #PdfSigner(PdfReader, OutputStream, StampingProperties)} instead.
 */
@Deprecated
public PdfSigner(PdfReader reader, OutputStream outputStream, boolean append) throws IOException

而是在文件系统中为此提供一个临时文件:

String temporaryFile = pdfAssinado.getAbsolutePath() + ".tmp";
PdfSigner signer = new PdfSigner(reader, os, temporaryFile, true);

对于您的 500MB 文件,这将再次减少 500MB 以上的内存使用量。


签署 500 MB 文件时,上述更改将减少超过 1 GB 的内存占用。我不知道这是否足够,但这至少应该大大减少内存需求。