`

【Itext】解决Itext5大并发大数据量下输出PDF发生内存溢出outofmemery异常

阅读更多

关键字 itext5 outofmemery 内存溢出 大数据 高并发 多线程 pdf 导出 报表 itext 并发

 

大数据量高并发的时候,Itext5会发生内存溢出,outofmemery异常,经过大规模的内存检查,发现Itext在生成表格的时候,使用了很多的HashMap做数据存储,从而造成了内存爆满,没有及时释放掉,虽然官方说可以利用table.setComplite(false)这个方法去释放内存,但是经过实际演示,发现效果不佳,因为高并发的时候,此方法失效。1000个用户同时访问服务器,它根本来不及释放。此时我们就需要增加一个方法,自己去释放掉那些已经加入到table中的Cell对象,使得系统平稳运行。只要释放及时,几万几十万并发都没问题?当然最好先扔给它2G jvm,不要太吝啬。生成pdf的时候,cpu占用比较高,因为IO操作。不过用了本文方法,内存几乎很平稳。这就够了。

 

在读<<iText in Action 2nd>4.3节(Dealing with large tables)的时候,书上写道:itext5PdfPTable实现了ILargElement的接口,只需要我们手动设置datatable.setComplete(false);之后,它就可以自动将表格元素输出到document中,但是,对,就是这个但是!!我们的cell之多,多到它来不及去放进去,比如我并发100个线程去访问它,用原来的官方的方法,别说tomcat受不了,was也照样挂掉,给他8个G,它也照样吃掉,而且服务卡死。你说这样的产品放出去,我放心不?

 

于是,我大量Google,大量百度,大量Csdn,大量JavaEye,多少次的说多了都是泪,最后,yes,就是最后,我Tm什么法子都想了,list清空,指针赋null,优化程序循环,优化bufferedOutPutStream输出,优化下载,就差给Itext作者写信了!!现在解决了这个高并发的问题,可以边生成边输出到IO磁盘,防止以前高并发,大家发生死锁,围着内存和IO卡死。

 

然后无名小卒兄居然也遇到了这个问题,而且这个博客我都不知道怎么搜到的,他中间用到了自己写的一个方法,根据行号去定次数,比如定义1000行一次释放table,将table先放到document,正好document中查到了你的table用datatable.setComplete(false)方法,于是它就开始往文件里面搬运数据,之后,我们删掉这些已经放进去的元素,用table.deleteBodyRows();好,这样产生了一个新的问题,就是每隔1000行,产生一个表头,于是无名兄又用到了table.setSkipFirstHeader(true);ok至此,解决全部问题,但是后续问题,无名兄遇到了,万一某一行刚刚好是最后一页,那么后续的表格没有了表头,这个问题我没遇到,因为我用到了另一个方法,就是  datatable.setHeaderRows(headerRows);// 设置头几行为表头(已经判断好了前几行为表头),这样我们就搞定了这个itext内存溢出的大问题!!也许很多人都不会遇到这个错误,但是我保证这个内存溢出会让你恨死Itext。

 

代码实现如下贴出,放在你生成表格的逻辑里。

 

 

[java] view plaincopy
 
  1. //for循环中添加如下代码  
  2. int _MAX_ROWS = 1000;//最大行数,之后清理  
  3. int row_count = 0;//初始值  
  4. if (++row_count % _MAX_ROWS == 0) {  
  5.                            //datatable是我的一个PdfPTable的new出来的一个实例                             
  6.                            // add table to Document  
  7.                            document.add(datatable);  
  8.                            // delete _MAX_ROWS from table to free memory  
  9.                            datatable.deleteBodyRows();  
  10.                            // let iText manage when table header written  
  11.                            datatable.setSkipFirstHeader(true);//防止释放后一页出现两次表头。  
  12. }   

 

 

或者另一种释放的算法:

 

 

[java] view plaincopy
 
  1. int fregmentSize = 1000;// 释放内存的最大行号,过了这个行号,直接删掉表格,往下继续生成,释放内存空间  
  2.   int k = 0;  
  3.   for (int i = 0, h = bodys.size(); i < h; i++) {  
  4.  if (i != 0 && i % fregmentSize == fregmentSize - 1) {  
  5.                             System.out.println("第[ " + (i + 1) + " ]行进行内存释放 " + ((k++) + 1) + " th");  
  6.                             document.add(datatable);  
  7.                             datatable.deleteBodyRows();  
  8.                             datatable.setSkipFirstHeader(true);  
  9.                         }  
  10. //.... 表格处理  
  11.   }  


其中,经过我的调试,发现一个问题:当你把需要清理的行数设置的越低,比如设置为100行,下面就会出现一些不爽的地方,如果改为1000以上,就没问题的,就是当你的前一页最下方那一行无法装下你的某行数据,需要换一页写入下一页,就是拆分行的话,你强制进行splitRow(false),会发生数据丢失。所以建议不要去 不让拆分表格 (或者你可以调大一些到1000行再去清理表格,别100行就清理,还不够内存累的)。比如下面这样是我推荐的方法,就让他在上一页一些,下一页一些呗,而且这种极限的情况很少发生,比如如下的demo,那就是我在100个线程并发访问生成5800行*33列的情况下搞的一个比较另类的pdf,前一页留一点,后一页留一点使得打印的时候更加美观,何必非要前一页留那么多空白:

 

 

1.清理缓存并换页,第200行数据跑到了下一页,199行之后留了空白出来。(此种不推荐)

 


 

2.清理缓存不换页(推荐此种)事实证明,当你把代码中的fregmentSize的值上升到1000行一清理,就不会出现换页的问题。大家量力而行把。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics