1. 程式人生 > >【Itext】解決Itext5大併發大資料量下輸出PDF發生記憶體溢位outofmemery異常

【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