上週排查了一個線上問題,主要現象是CPU佔用太高,jvm old區佔用太高,同時頻繁fgc,我簡單排查了下就草草收場了,可是事後我對這個問題又進行了複查,發現問題沒有那麼簡單,下面跟着我一塊兒分析一下究竟是怎麼回事?java
必定要先讀完上篇文章cpu使用率太高和jvm old佔用太高排查過程spring
過後再看dump文件注意到最大的對象是一個ArrayList,裏面幾乎都是ElasticSearchStatusException對象apache
但是發生這個異常的操做上次已經被我定位到了,數據漏斗只有產品、運營等內部人員使用,經過使用頻率推測,不該該有那麼多對象。我猜測是否是代碼中存在死循環,但沒有找到。沒辦法只能在測試環境進行場景復現了。數組
經過上次排查到是es查詢了不存在的索引致使異常,因此就把查詢es的索引寫死一個不存在的,最初嘗試寫個單測,但一直不能復現問題,因此只好部署到測試環境,在本地經過遠程debug 調試程序jvm
遠程debug到斷點時,發現源碼對不上工具
而後發現有可選擇的源碼,這裏是關鍵測試
從org.apache.commons.lang:2.5jar包切換到springsource.org.apache.commons.lang:2.1.0包後,居然可以和測試環境對得上,但是代碼中明明引用的commons.lang:2.5的包,這裏說明在項目中類加載的時候,ExceptionUtils這個class文件並非從commons.lang中加載的,而是從springsource包中加載的 關於類文件加載的問題咱們先放到後面,先找代碼的問題debug
看到這裏,眼尖的朋友應該已經發現了bug的所在,那麼恭喜你。若是沒發現的朋友,不要着急,跟我一步一步來。咱們繼續debug跟進代碼的 getCause方法,能夠看到經過遍歷異常名字的數組驗證是否在拋出的異常中存在調試
這些異常方法中的getRootCause方法,存在ElasticSearchStatusException的父類ElasticsearchException中日誌
咱們看下這個方法,主要找最根本的異常緣由,有則返回,沒有就返回當前的異常
繼續跟代碼,cause不爲null,返回這個異常
bug代碼定位
這個getThrowables方法,裏面有個while循環,判斷條件只進行了非空判斷,不爲null就添加到list中,注意觀察我截圖的時刻,list的大小 8萬多,其實遠遠不止會看開頭dump文件的大對象,是一個ArrayList,裏面有大量的ElasticSearchStatusException對象
其實到這裏已經定位到了FGC的真兇,判斷條件沒有排除返回的異常是已經添加到list中的異常,因此會一直循環添加,形成堆內存佔用滿了,FGC回收不掉這些對象,由於ArrayList一直持有他們的引用
正確代碼應該以下面這樣,因此開源工具庫也是會有bug的,用的時候多加註意
~~~javapublic static Throwable[] getThrowables(Throwable throwable) {List list = new ArrayList();// 這裏的判斷條件應該加上 list.contains(throwable) == falsewhile (throwable != null && list.contains(throwable) == false) {list.add(throwable);throwable = ExceptionUtils.getCause(throwable);}return (Throwable[]) list.toArray(new Throwable[list.size()]);}~~~
原本咱們就沒想用springsource的方法,只是類加載的時候加載錯了,那看下commons.lang包下的方法是否正確呢?能夠看到這個包的方法是正確的,考慮到了這個問題
springsource的commons.lang包在2.2版本已經修復了這個問題 jar包最好引用最新的
上面咱們留了一個jvm加載class文件的問題,咱們知道jvm加載class的時候,若是存在包名和類名徹底同樣,先加載一個後,另外的就不會再被加載了。
經查看兩個ExceptionUtils確實包名類名徹底一致
其實經過前面的debug和代碼分析,已經能肯定項目加載的ExceptionUtils.class文件來自springsource包,但仍是想經過必定手段驗證一下。
其實jvm提供類相似的功能參數,修改項目啓動腳本,添加jvm參數 -XX:+TraceClassLoading 而後重啓項目並繼讓異常重現【這裏要讓觸發異常重現,是由於是運行時異常】,並查看日誌,查看ExceptionUtils.class的加載信息
能夠看到確實和咱們推測的同樣
其實這裏還能夠深刻研究jvm的類加載機制,類加載器加載順序,雙親委派模型等
經過 mvn dependency:tree 查看jar包依賴狀況,排除掉不用的jar包
到這裏這個問題的排查應該告一段落了,從排查過程當中學到了很多,場景復現,梳理思路,用文章分享出來雖然說碼字不易很耗時,但這是對本身的一種總結,裏面仍是有不少樂趣與收穫的。
後續會繼續分享一些和廣告系統相關的文章,敬請期待!
歡迎關注公衆號 【天天曬白牙】,獲取最新文章,咱們一塊兒交流,共同進步!