最近客戶那邊報了一個問題上來,問題:門店反饋JPOS界面查詢“分類銷售報表”,查詢一個月的數據一直卡在查詢界面,查詢一天的數據,至少要3分鍾,據了解門店之前是沒有問題的。
我拿到客戶現場的數據以及日志,把現場數據安裝到我的開發環境中,查看下日志,日志沒有任何報錯,收銀員的操作也沒有任何不規范之處,然后就果斷不管日志了,重現了問題,發現沒有報錯,進度條一直顯示“正在加載數據”,所以想到用visualVm中的線程監測工具對他進行監測,監測結果顯示,線程沒有死鎖,一直處在Runnable狀態,入下圖:
線程監測沒得到什么頭緒,然后想看看堆的情況,然后打開visualVM對內存進行監控,監控了一段時間終於有了一點頭緒了,入下圖:
從visualVM對垃圾收集器的監控來看,新生代Eden區,老年代Old區都呈現鋸齒狀,顯然這個有問題,不正常。Eden區不斷的出現新生對象,等到下一次MinorGC的時候Eden區和Survivor區的存活的對象會通過空間擔保機制直接進入老年代,老年代內存達到默認FullGC的百分比的時候就會觸發一次,監控還顯示了Eden區會有非常巨大的對象產生(是Eden區直接從13M達到80M)。所以光從這個統計也不能知道具體怎么回事,所以決定對該進程的垃圾回收信息以及堆進行一個分析。
用-XX:+PrintGCDetails打印出垃圾收集信息,並且使用JConsole.exe中的MBean創建一個堆轉儲文件(用Eclipse Memory Analysis進行分析)。垃圾收集的日志分析得到,開始的時候Eden區的內存大小只有4M,之后慢慢的擴大,這個擴大Eden區的操作顯然是需要時間的,老年代也是如此,所以不如直接把Eden區和老年代的大小固定為他們各自的最大的值。之后垃圾收集就很規律,Eden區不斷的產生新的對象在老年代中的FullGC中被回收點,雖然虛擬機在不斷的做創建和銷毀的操作,但是從來沒有溢出。所以垃圾收集日志對我的幫助也不大。
下面看堆轉儲文件的分析,使用JConsole.exe產生一個堆的快照,H:\jpos.hprof,然后在Eclipse中用插件Eclipse Memory Analysis打開進行分析,效果如下:
上面的圖片說明了ConCurrentCache以及SessionFactoryImpl在堆里面占的比例是最大的,所以在Eclipse Memory Analysis分析器中顯示着兩種對象很可疑,那為什么會產生這2種對象呢?暫時不解釋。看到這里,我第一反應是Hibernate的緩存導致的,但是想了下,我的hql只有一條,用來統計數據的,也不需要不斷的生成對象,不斷的銷毀對象,當然想到了這一點,我離問題的真正原因也更近了(實際是更遠了,我跑偏了方向,只是此路不同,所以我離真相更近了),我萎了驗證到底是不是Hibernate緩存導致的,我決定把Hql轉換成Sql,直接調用JDBC底層的方法進行統計數據的查詢,結果出人意料,跟我想的完全不一樣,就算我換做JDBC查詢,速度也一樣的慢,VisualVM中的VisualGC插件顯示垃圾收集器一樣的鋸齒狀出現,到這里,有經驗的大牛應該知道了,問題在哪里了吧?數據庫三張表,每張表2w條數據,也不算大數據。
最后我想這應該數據庫字段沒有加索引導致的,給每個表出現的字段加上索引,問題馬上解決。
現在我們再看上面為什么會不斷的生成對象,再銷毀對象。原因是JPOS收銀機用的是內嵌式Derby數據庫,當執行這個操作的時候,內存中會不斷的加載數據匹配,匹配完一批再匹配另一批,直到把所有的數據都匹配完為止,所以VisualGC中會顯示不斷的在Eden區創建對象,然后不斷的通過擔保機制到老年代,然后做FullGC,這也就是為什么ConCurrentCache以及SessionFactoryImpl在堆里面占的比例會那么大。
雖然問題的解決繞了很多彎路,但是至少學習到了很多知識,熟悉了各種工具的使用。