Apache PdfBox

業(yè)務(wù)敘述

技術(shù)是為了業(yè)務(wù)服務(wù)的上煤,所以討論技術(shù)的時候離不開業(yè)務(wù)場景板辽。故此先進(jìn)行簡述:
最近在做一個從數(shù)據(jù)庫讀取pdf文件并且解析成文字進(jìn)行分析的功能僵刮。由于本人對垃圾回收也是略有涉獵攒霹,因此代碼設(shè)計上基本不存在本應(yīng)該回收但是卻無法回收的情況厘托。然而,這個程序用springboot啟動后一段時間就會出現(xiàn)oom錯誤流炕,錯誤日志如下:

java.lang.OutOfMemoryError: Java heap space
    at oracle.sql.BLOB.getBytes(BLOB.java:217)
    at org.apache.ibatis.type.BlobTypeHandler.getNullableResult(BlobTypeHandler.java:43)
    at org.apache.ibatis.type.BlobTypeHandler.getNullableResult(BlobTypeHandler.java:28)
    at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:81)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.applyAutomaticMappings(DefaultResultSetHandler.java:521)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue(DefaultResultSetHandler.java:402)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForSimpleResultMap(DefaultResultSetHandler.java:354)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValues(DefaultResultSetHandler.java:328)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSet(DefaultResultSetHandler.java:301)
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:194)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
    at com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor.doQuery(MybatisSimpleExecutor.java:67)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
    at sun.reflect.GeneratedMethodAccessor34.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)
    at com.sun.proxy.$Proxy60.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:223)
    at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:158)
    at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:76)
    at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:61)
    at com.sun.proxy.$Proxy65.findAttachments(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor35.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)

從日志中可以看出澎现,在獲取數(shù)據(jù)庫中的Blob數(shù)據(jù)時,出現(xiàn)了Java heap space不足每辟,也就是堆滿了剑辫。在此科普一下幾種OOM的情況:

PermGen space

發(fā)生這種問題的原意是程序中使用了大量的jar或class,使java虛擬機(jī)裝載類的空間不夠渠欺,與Permanent Generation space有關(guān)妹蔽。常用的解決方法是調(diào)整XX:PermSize和XX:MaxPermSize參數(shù)的大小。

unable to create new native thread

顧名思義就是不能創(chuàng)建native的線程。遇到這種情況還是比較復(fù)雜的胳岂,具體遇到了再分析编整。

Java heap space

發(fā)生這種問題的原因是java虛擬機(jī)創(chuàng)建的對象太多,在進(jìn)行垃圾回收之間乳丰,虛擬機(jī)分配的到堆內(nèi)存空間已經(jīng)用滿了闹击,與Heap space有關(guān)。這是比較常見的情況成艘。

分析錯誤

jvisualvm

jvisualvm是一個性能分析工具赏半,自從 JDK 6 Update 7 以后已經(jīng)作為 Oracle JDK 的一部分,位于 JDK 根目錄的 bin 文件夾下淆两。jvisualvm自身要在 JDK6 以上的版本上運(yùn)行断箫,但是它能夠監(jiān)控 JDK1.4 以上版本的應(yīng)用程序。
雖然jdk9之后這個工具被移除了秋冰,但是恰好當(dāng)前項目是java 8的仲义,jvisualvm依然可以使用。
具體用法:在jdk的bin目錄下啟動剑勾,或者如果環(huán)境變量已經(jīng)配好埃撵,直接在控制臺下輸入jvisualvm也可以啟動
當(dāng)然我這里不是寫怎么用這個工具,具體操作不細(xì)述虽另。有了工具就可以開工了暂刘。以下是整個分析的過程。

懷疑1:byte數(shù)組占用內(nèi)存捂刺,未及時清理

這個懷疑很簡單谣拣,因此日志報的是在讀取blob格式數(shù)據(jù)時內(nèi)存溢出。因此我第一反應(yīng)就是在當(dāng)字節(jié)數(shù)組使用完后進(jìn)行一次gc族展。
啟動應(yīng)用后java堆如下圖:


圖1

圖上可以明顯看出來成過山車式的效果森缠,運(yùn)行一段時間后再次出現(xiàn)OOM。利用jvisualvm的dump功能進(jìn)行分析仪缸,在山頂和山崖下分別dump堆信息進(jìn)行比對(忘記截圖了)發(fā)現(xiàn)大字節(jié)數(shù)組差異不明顯贵涵,顯然問題不是字節(jié)數(shù)組造成的。

懷疑2:奇技淫巧stream會不會有問題

由于喜歡用stream寫程序恰画,恰好分析這段代碼又是在forEach塊中宾茂,因此懷疑是不是這個寫法有坑。于是改成普通的循環(huán)锣尉,再次查看堆:


普通的for循環(huán)

從統(tǒng)計學(xué)上看刻炒,這次的結(jié)果和上次沒有很大區(qū)別,出現(xiàn)了一段400M維持的情況自沧。

懷疑3:正則解析產(chǎn)生大量臨時對象

比對heap圖中波峰段和日志情況,基本上每個波峰段都是在解析文件時出現(xiàn)∧聪幔看過老夫github page的小伙伴應(yīng)該知道爱谁,老夫號稱正則小王子,所以解析文字必然使用正則孝偎。到這時候我是有點擔(dān)心的访敌,雖然我理解中正則引擎是計算密集型,但是畢竟沒讀過源碼衣盾,萬一恰好是坑咋辦寺旺?但是再細(xì)讀代碼和分析dump文件,我感覺事情沒那么簡單(當(dāng)然也有不敢相信就是這么一回事)因為所有的pattern對象我都是預(yù)編譯好的势决,在解析的時候了不起生成一些matcher對象阻塑,再新建一些String或者char數(shù)組。如果是這樣果复,那么波峰應(yīng)該是一段明顯的斜線上升陈莽,而從圖上又可以看出在解析的時候波峰呈平緩上升。故此我懷疑是在解析文件之前讀取環(huán)節(jié)出的問題虽抄。

懷疑4:pdfBox解析pdf時產(chǎn)生大量冗余

由于文件讀取速度非匙吒椋快,而jvisualvm的時間軸是秒為單位迈窟,在以毫秒為單位的程序中很難看出端倪私植,因此在文件讀取后添加Thread.sleep(2000)方法。

結(jié)論

最終通過比對日志時間和heap時間軸發(fā)現(xiàn)是pdfBox解析文件時產(chǎn)生大量臨時對象(由于pdfBox封裝的很厲害车酣,整個解析代碼非常少兵琳,因此不會是我人為的操作導(dǎo)致的),因此把gc標(biāo)志放到文件讀取完成后骇径。再次查看heap躯肌,效果如下圖:


image.png

至此問題算是解決了。由于是上班時間解決的破衔,因此沒有截很多圖清女,中間略去通過日志和heap圖形化界面對比找到罪魁禍?zhǔn)椎牟襟E∥福看不懂的小伙伴多思考一下了哈~
整體來說這是本人第一次遇到oom并解決的過程嫡丙,還是蠻爽的,畢竟很多時候我們學(xué)習(xí)時都是只聞其聲不見其廬山真面目读第。但是真的遇到了也不需要驚慌曙博,但凡別人可以,自己不可以怜瞒,那都是細(xì)節(jié)小問題父泳;如果別人不可以般哼,自己可以,那是別人沒拷貝全(想到今天組里小姑娘CV老夫代碼時的少初始化了一個變量惠窄,折騰一下午)蒸眠;別人不行,自己也不行杆融,折騰折騰楞卡,也許就行了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脾歇,一起剝皮案震驚了整個濱河市蒋腮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌藕各,老刑警劉巖池摧,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異座韵,居然都是意外死亡险绘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門誉碴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宦棺,“玉大人,你說我怎么就攤上這事黔帕〈蹋” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵成黄,是天一觀的道長呐芥。 經(jīng)常有香客問我,道長奋岁,這世上最難降的妖魔是什么思瘟? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮闻伶,結(jié)果婚禮上滨攻,老公的妹妹穿的比我還像新娘。我一直安慰自己蓝翰,他們只是感情好光绕,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畜份,像睡著了一般诞帐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爆雹,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天停蕉,我揣著相機(jī)與錄音愕鼓,去河邊找鬼。 笑死谷徙,一個胖子當(dāng)著我的面吹牛拒啰,可吹牛的內(nèi)容都是我干的驯绎。 我是一名探鬼主播完慧,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剩失!你這毒婦竟也來了屈尼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拴孤,失蹤者是張志新(化名)和其女友劉穎脾歧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體演熟,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡鞭执,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了芒粹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兄纺。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖化漆,靈堂內(nèi)的尸體忽然破棺而出估脆,到底是詐尸還是另有隱情,我是刑警寧澤座云,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布疙赠,位于F島的核電站,受9級特大地震影響朦拖,放射性物質(zhì)發(fā)生泄漏圃阳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一璧帝、第九天 我趴在偏房一處隱蔽的房頂上張望捍岳。 院中可真熱鬧,春花似錦裸弦、人聲如沸祟同。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晕城。三九已至,卻和暖如春窖贤,著一層夾襖步出監(jiān)牢的瞬間砖顷,已是汗流浹背贰锁。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留滤蝠,地道東北人豌熄。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像物咳,于是被迫代替她去往敵國和親锣险。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355