这个问题困扰了我整整两个星期!!
关键字 itext5 outofmemory 内存溢出 大数据 高并发 多线程 pdf 导出 报表 itext 并发
在阅读<
于是我开始疯狂 Google、百度、CSDN、JavaEye,到处查 iText5 内存溢出、PDF 导出高并发、多线程报表生成 的解决方案。说多了都是泪,最后几乎什么办法都试过了:清空 list、把指针赋 null、优化程序循环、优化 BufferedOutputStream 输出、优化下载流程,差点都想给 iText 作者写信了!!不过最终还是把这个高并发 PDF 导出的问题解决了:实现了边生成边输出到 IO 磁盘,避免了之前高并发场景下线程之间争抢内存和 IO 资源,导致死锁、卡死甚至服务器崩溃的问题。
后来无名小卒兄居然也遇到了同样的问题,而且这个博客我都不知道自己当时是怎么搜到的。他在处理过程中用了一个自己写的方法:根据行号来控制执行次数。比如可以定义为每 1000 行释放一次 table,先把 table 放到 document 中,因为这时 document 里已经能识别到你的 table 了,再通过 datatable.setComplete(false) 方法,让它持续把数据写入文件。等这一批数据写完之后,再把已经写进去的元素删除,也就是使用 table.deleteBodyRows();。这样一来,iText5 大数据表格导出时的内存压力确实能明显缓解,不过同时又会引出一个新的问题:每隔 1000 行就会重新生成一次表头。于是无名兄又加上了 table.setSkipFirstHeader(true);,到这里,前面几个关键问题基本都解决了。
int _MAX_ROWS = 1000;//最大行数,之后清理 int row_count = 0;//初始值 if ( row_count % _MAX_ROWS == 0) {System.out.println("row_count % _MAX_ROWS=" row_count % _MAX_ROWS);// add table to Document document.add(datatable); // delete _MAX_ROWS from table to free memory datatable.deleteBodyRows();// let iText manage when table header written //datatable.setSkipFirstHeader(true);
}或者
int fregmentSize = 1000;// 释放内存的行号int k = 0;for (int i = 0, h = bodys.size(); i < h; i ) { if (i != 0 && i % fregmentSize == fregmentSize - 1) {System.out.println("第[ " (i 1) " ]行进行内存释放 " ((k ) 1) " th");document.add(datatable);datatable.deleteBodyRows();datatable.setSkipFirstHeader(true);} //.... 表格处理} 经过我的实际调试,我发现还有一个非常容易踩坑的问题:如果前一页底部剩余空间已经不足以放下一整行数据,那么写入时就必须自动换到下一页;这时候如果你强制使用 splitRow(false) 来禁止拆分行,就很可能导致数据丢失。所以更建议不要刻意禁止表格拆分。当然,如果你确实担心 iText PDF 导出过程中的内存占用,也可以适当把清理 table 的阈值调大一些,例如累计到 1000 行再清理,而不是 100 行就清一次,否则表格数据还没积累起来,内存与分页压力反而更明显。像下面这种处理方式就是我更推荐的做法:允许表格内容在上一页放一部分,下一页继续接着放即可。其实这种极限情况并不常见。比如下面这个 demo,就是我在 100 个线程并发访问、生成 5800 行 * 33 列数据时做出来的一个比较特殊的 PDF,前一页保留一点、后一页承接一点,打印出来反而更美观,完全没必要为了整行不拆分,硬让前一页留下大块空白。
1.清理缓存并换页,第200行数据被放到了下一页,199行之后出现了明显空白。(不推荐)

2.清理缓存但不强制换页(推荐)事实证明,当你把 fregmentSize 的值提升到每 1000 行清理一次时,通常就不会再出现这种分页空白的问题。具体设置多大,大家可以根据服务器内存和并发量自行权衡。

落雨(感谢无名兄提供的这个非常实用的思路!!先 add,再 delete,再继续 add,确实有效。good)
394263788
2013年9月11日 10:04:12
