调整COSWriter解决X-easypdf / PDFBOX生成大量数据时OOM问题

背景

业务需要生成一个15W数据左右的PDF交易报表。希望我们写在一个文件里,不拆分成多个PDF文件。

使用的技术组件

        <dependency><groupId>wiki.xsx</groupId><artifactId>x-easypdf-pdfbox</artifactId><version>2.11.10</version></dependency>

生成PDF方法

testPDF: 使用xeasypdf实现未做修改

testDynamicPdf: 使用了修改后的方法实现

package wiki.xsx.core.pdf.doc;import org.junit.Test;
import wiki.xsx.core.pdf.component.table.XEasyPdfCell;
import wiki.xsx.core.pdf.component.table.XEasyPdfRow;
import wiki.xsx.core.pdf.component.table.XEasyPdfTable;
import wiki.xsx.core.pdf.component.text.XEasyPdfText;
import wiki.xsx.core.pdf.handler.XEasyPdfHandler;
import wiki.xsx.core.pdf.mark.XEasyPdfWatermark;public class XEasyPdfDynamicTest {public static final int GENERATE_PAGE = 10000;@Test//原生办法,最好别执行,会内存溢出。public void testPdf() {// 定义pdf输出路径String outputPath = "D://out.pdf";XEasyPdfText titleText = XEasyPdfHandler.Text.build("明细");titleText.setHorizontalStyle(XEasyPdfPositionStyle.CENTER);titleText.setFontSize(32);titleText.setMarginTop(15);XEasyPdfWatermark watermark = XEasyPdfHandler.Watermark.build("账单");// 如果需要动态加Page,需要使用定制的对象;XEasyPdfDocument document = XEasyPdfHandler.Document.build();document.setGlobalHeader(XEasyPdfHandler.Header.build(titleText));document.setGlobalWatermark(watermark);int[] cellWidth = {130, 80, 80, 262};for (int current = 0; current < GENERATE_PAGE; current++) {XEasyPdfPage xEasyPdfPage = generatePage(current, cellWidth);document.addPage(xEasyPdfPage);}document.save(outputPath).close();}@Testpublic void testDynamicPdf() {// 定义pdf输出路径String outputPath = "D://out.pdf";XEasyPdfText titleText = XEasyPdfHandler.Text.build("明细");titleText.setHorizontalStyle(XEasyPdfPositionStyle.CENTER);titleText.setFontSize(32);titleText.setMarginTop(15);XEasyPdfWatermark watermark = XEasyPdfHandler.Watermark.build("账单");// 如果需要动态加Page,需要使用定制的对象;XEasyPdfDynamicPdfDocument document = new XEasyPdfDynamicPdfDocument();document.setGlobalHeader(XEasyPdfHandler.Header.build(titleText));document.setGlobalWatermark(watermark);int[] cellWidth = {130, 80, 80, 262};for (int current = 1; current <= GENERATE_PAGE; current++) {XEasyPdfPage xEasyPdfPage = generatePage(current, cellWidth);document.addPage(xEasyPdfPage);if (current % 100 == 0) {document.flush();}}document.dynamicSave(outputPath, new XEasyPdfDynamicPage(10000, document)).close();}public static XEasyPdfPage generatePage(long current, int[] cellWidth) {// 这里构建一下页数;XEasyPdfTable table = XEasyPdfHandler.Table.build();XEasyPdfPage page = XEasyPdfHandler.Page.build();table.setMarginTop(30);table.setMarginLeft(20);table.enableCenterStyle();XEasyPdfRow headRow = XEasyPdfHandler.Table.Row.build();XEasyPdfCell headCell1 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[0]);headCell1.addContent(XEasyPdfHandler.Text.build("卡号"));XEasyPdfCell headCell2 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[1]);headCell2.addContent(XEasyPdfHandler.Text.build("下标"));XEasyPdfCell headCell3 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[2]);headCell3.addContent(XEasyPdfHandler.Text.build("金额"));XEasyPdfCell headCell4 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[3]);headCell4.addContent(XEasyPdfHandler.Text.build("描述"));headRow.addCell(headCell1, headCell2, headCell3, headCell4);table.addRow(headRow);page.addComponent(table);for (int i = 0; i < 14; i++) {// 14行一页;XEasyPdfRow row = XEasyPdfHandler.Table.Row.build();row.setHeight(50);XEasyPdfCell cell1 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[0]);cell1.addContent(XEasyPdfHandler.Text.build("123456"));XEasyPdfCell cell2 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[1]);cell2.addContent(XEasyPdfHandler.Text.build("j-" + current + ":i-" + i));XEasyPdfCell cell3 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[2]);cell3.addContent(XEasyPdfHandler.Text.build("20.1"));XEasyPdfCell cell4 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[3]);cell4.addContent(XEasyPdfHandler.Text.build("说明"));row.addCell(cell1, cell2, cell3, cell4);table.addRow(row);}return page;}
}

testPdf执行情况

Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: Java heap spaceat java.base/java.security.AccessController.wrapException(AccessController.java:828)at java.base/java.security.AccessController.doPrivileged(AccessController.java:716)at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$176/0x000001bb3a9bd290.run(Unknown Source)at java.base/java.security.AccessController.executePrivileged(AccessController.java:776)at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)at java.base/java.lang.Thread.run(Thread.java:833)java.lang.OutOfMemoryError: Java heap space
11月 16, 2023 4:15:07 下午 org.apache.pdfbox.cos.COSDocument finalize
警告: Warning: You did not close a PDF DocumentProcess finished with exit code -1

从JVM监控可以看出CPU与内存占用会随着PDF文件写入而逐渐增大。【很正常,因为他无法释放内存】

testDynamicPdf运行情况

源代码

基于源码fork的仓库地址【源码我没权限改,所以fork了一个】:

x-easypdf: 一个用搭积木的方式构建pdf的框架(基于pdfbox/fop)icon-default.png?t=N7T8https://gitee.com/crazyAsm/x-easypdf分支:FEATURE_Dynamic_Generate

OOM原因

超过1万页的数据,使用原版的COSWriter类会占用大量内存。

COSWriter在写文件时,会使用doWriterBody方法写入PDF的基础信息。如下:

protected void doWriteBody(COSDocument doc) throws IOException{COSDictionary trailer = doc.getTrailer();COSDictionary root = trailer.getCOSDictionary(COSName.ROOT);COSDictionary info = trailer.getCOSDictionary(COSName.INFO);COSDictionary encrypt = trailer.getCOSDictionary(COSName.ENCRYPT);if( root != null ){addObjectToWrite( root );}if( info != null ){addObjectToWrite( info );}doWriteObjects();willEncrypt = false;if( encrypt != null ){addObjectToWrite( encrypt );}doWriteObjects();}

可以看到会写入的信息有root、基础信息、与加密信息【因为这个不咋占内存,这里就不展开说明了】;然后会执行doWriteObjects();

 第一次写入时可以看出,写的是Type\Version\Page\MetaData这四个信息;

分别对应PDF文件内容的Type\Version\Page\MetaData:f

根据PDF的规则,实际Page栏的4 0 R 代表 第一页对应内容在4 0 obj 位置,有多少页Page就会有多少个引用键。4 0 obj 对应的是第一页的内容,内容又是由一堆引用键组成的。COSWriter的问题也就在这里,只要页数够大,内容够多,这里就会占用大量内存。

解决思路

既然内存占用原因是写入时在内存中存放了太多的内容,那么解决思路也就很容易得出来:一页一页写就行了。

因为我用的事X-EasyPdf 所以基于这个改造了一下。【源码自己看下git仓库吧】

XEasyPdfDynamicCOSWriter:基于COSWriter改造的类目的:在doWriteObjet时,动态加载Page并写入;
XEasyPdfDynamicPage:动态页的实现,结合XEasyPDFDocument的flush方法,借助临时文件增量写页内容。
XEasyPdfDynamicPdfDocument:增加了个实现,写文件改用XEasyPdfDynamicCOSWriter类。

参考文章

https://zxyle.github.io/PDF-Explained/resources/pdf_reference_1.7.pdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/196147.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Swift制作打包framework

新建framework项目 设置生成fat包&#xff0c;包括模拟器x86_64和arm64 Buliding Settings -> Architectures -> Build Active Architecture Only 设置为NO 设置打包环境&#xff0c;选择release edit Scheme -> run -> Build configuration 设置为 Release 设置…

LLM大模型权重量化实战

大型语言模型 (LLM) 以其广泛的计算要求而闻名。 通常&#xff0c;模型的大小是通过将参数数量&#xff08;大小&#xff09;乘以这些值的精度&#xff08;数据类型&#xff09;来计算的。 然而&#xff0c;为了节省内存&#xff0c;可以通过称为量化的过程使用较低精度的数据类…

高级数据结构——树状数组

树状数组&#xff08;Binary Index Tree, BIT&#xff09;&#xff0c;是一种一般用来处理单点修改和区间求和操作类型的题目的数据结构&#xff0c;时间复杂度为O(log n)。 对于普通数组来说&#xff0c;单点修改的时间复杂度是 O(1)&#xff0c;但区间求和的时间复杂度是 O(n…

ssrf学习笔记总结

SSRF概述 ​ 服务器会根据用户提交的URL 发送一个HTTP 请求。使用用户指定的URL&#xff0c;Web 应用可以获取图片或者文件资源等。典型的例子是百度识图功能。 ​ 如果没有对用户提交URL 和远端服务器所返回的信息做合适的验证或过滤&#xff0c;就有可能存在“请求伪造”的…

mock测试数据

1.下载一个jar 架包 地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1G5rVF5LlIYpyU-_KHsGjOA?pwdab12 提取码&#xff1a;ab12 2.配置当前电脑java环境变量 3.在同一文件目录下创建json 数据4.在终端切换到当前目录下启动服务&#xff0c; java -jar ./moco-r…

java+ 如何动态配置业务规则组

思路 1. 实现在页面上的动态配置规则组&#xff08;2张数据表枚举类serviceimplaction&#xff09; 2. 从数据库中表staffmoverules&#xff08;规则明细表&#xff09;或者staffmovetyperule&#xff08;规则组表&#xff09; &#xff0c;根据传入类型&#xff0c;取出规则编…

计算机网络——WLAN简解

1. WLAN的发展历程 ❓ WLAN和WIFI有什么区别。 &#x1f604; 具体来说&#xff0c;WALN是抽象的概念&#xff0c;代表这无线局域网这一类技术&#xff0c;而WIFI则是具体的具体技术标准&#xff0c;虽然在生活中&#xff0c;二者的表现是强相关的&#xff08;因为是使用的wifi…

Mysql中的进阶增删查改操作(二)

联合查询和合并查询 一.联合查询1.内连接2.外链接2.1左外连接2.2右外连接 3.自连接4.子查询5.合并查询 一.联合查询 步骤 1.进行笛卡尔积 2.列出连接条件 3.根据需求再列出其他条件 4.针对列进行精简(可以使用聚合函数) 我们先搭建一个多表查询的框架 这样一个多表查询就搭建出…

MatrixOne 实战系列回顾 | 建模与多租户

本次分享主要介绍MatrixOne建模与多租户相关内容。 1 建模 #1 与MySQL的区别 使用create table语句建表和MySQL建表语句基本相同&#xff0c;也有几点要注意。 MatrixOne暂不支持空间数据类型&#xff0c;其他数据类型在保持与 MySQL 命名一致的情况下&#xff0c;在精度与…

腾讯云轻量4核8G12M带宽服务器租用价格和S5实例报价

腾讯云4核8G服务器优惠价格表&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;轻量应用服务器4核8G12M带宽一年446元、529元15个月&#xff0c;阿腾云atengyun.com分享腾讯云4核8G服务器详细配置、优惠价格及限制条件&…

Elasticsearch:运用向量搜索通过图像搜索找到你的小狗

作者&#xff1a;ALEX SALGADO 你是否曾经遇到过这样的情况&#xff1a;你在街上发现了一只丢失的小狗&#xff0c;但不知道它是否有主人&#xff1f; 了解如何使用向量搜索或图像搜索来做到这一点。 通过图像搜索找到你的小狗 您是否曾经遇到过这样的情况&#xff1a;你在街…

VBA如何快速识别Excel单元格中的文本数字

Excel中一种非常特殊的数字&#xff0c;这些数字看似数字&#xff0c;其实是文本格式&#xff08;下文简称为文本数字&#xff09;&#xff0c;在单元格的左上角会有一个绿色小三角作为标志&#xff0c;如B1:B3单元格。 在编程时为什么需要区分普通数字和文本数字呢&#xff…

什么是Selenium?如何使用Selenium进行自动化测试?

什么是 Selenium&#xff1f; Selenium 是一种开源工具&#xff0c;用于在 Web 浏览器上执行自动化测试&#xff08;使用任何 Web 浏览器进行 Web 应用程序测试&#xff09;。   等等&#xff0c;先别激动&#xff0c;让我再次重申一下&#xff0c;Selenium 仅可以测试Web应用…

cvf_使用lora方法增强能力

cvf_使用lora方法增强能力 实验对比图最终代码简介详细解析实验对比图 最终代码 import paddle import numpy as np import pandas as pd from tqdm import tqdmclass FeedFroward(paddle.nn.Layer)

杭州-区块链前瞻性论坛邀请函​

2023密码与安全前瞻性论坛邀请函 生成合法节点或非法节点&#xff0c;测试共识协议

MySQL存储架构

连接管理与安全性 每个客户端连接都会在服务器进程中拥有一个线程&#xff0c;这个连接的查询只会在这个线程中执行。MySQL5.5以后支持了一个API叫线程池插件&#xff0c;可以用少量线程服务大量连接&#xff0c;因此不用每次都新建连接然后销毁。 客户端连接MySQL服务器时候&…

不允许你还没有了解哈希表、哈希桶、哈希冲突的解决,如何避免冲突

✏️✏️✏️今天给各位带来的是哈希桶、哈希冲突方面的知识。 清风的CSDN博客 &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; 动动你们发财的小手&#xff0c;点…

vscode中Chinese (Simplified)汉化无效解决方法

问题复现 之前已经下载了 Chinese (Simplified)插件并启用了&#xff0c;都是正常的中文简体。有时候打开vscode的时候&#xff0c;会发现汉化失效了&#xff0c;如图&#xff1a; 解决方法 依次点击 扩展&#xff08;Extensions&#xff09;— Chinese (Simplified) — 选…

flutter TabBar指示器

第一层tabView import package:jade/configs/PathConfig.dart; import package:jade/customWidget/MyCustomIndicator.dart; importpackage:jade/homePage/promotion/promotionPost/MyPromotionListMainDesc.dart; import package:jade/homePage/promotion/promotionPost/MyPr…

【Nacos】配置管理、微服务配置拉取、实现配置热更新、多环境配置

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Nacos 一、nacos实现配置管理1.1 统一配置管…