【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。


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


//for循环中添加如下代码
int _MAX_ROWS = 1000; //最大行数,之后清理
int row_count = 0; //初始值
if (++row_count % _MAX_ROWS == 0) {
        //datatable是我的一个PdfPTable的new出来的一个实例                           
        // 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);
        }
        //.... 表格处理
}

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

 

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




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




落雨(感谢无名兄给的很好的思路!!先添加了再删掉再添加。good)

394263788

2013年9月11日 10:04:12



发布了12 篇原创文章 · 获赞 35 · 访问量 8万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览