1. 程式人生 > >記錄一次jvm記憶體洩露的問題

記錄一次jvm記憶體洩露的問題

  前些天,運維告訴我剛上線的java服務佔用CPU過高。

       以下是發現解決問題的具體流程。

   1:通過#top命令檢視,我的java服務確實把CPU幾乎佔滿了,如圖

    

   可看到18400這個程序CPU佔用達到了1200%,這確實不太正常,那麼我們接下來分析到底哪些執行緒佔用了CPU

 

  2:通過#top -Hp 18400這條命令我們可以看到這個程序中執行緒的情況,部分截圖如下。

   

  通過截圖可以看到,前面的執行緒佔用的CPU是比較高的,那我們就具體分析這些執行緒 

 

  3:我們通過#jstack 18400>18400.txt命令將這個java程序的執行緒棧給抓出來,可以多抓幾次做個對比。

  我們以18414這個執行緒為例,將它轉成16進位制,linux可以用終端通過命令#printf "%x" 18414將執行緒id轉為16進製為47ee,那麼我們接下來在檔案裡找47ee這個執行緒在幹什麼,部分截圖如下

  

   我們可以看到47ee是個垃圾回收執行緒;我們對其他佔用CPU高的執行緒做相同的操作,發現都是GC執行緒。說明這個java服務一直在GC,這很不正常。那麼我們接下來分析GC情況。

 

  4:我們通過命令#jstat -gcutil 18400 1000 100

來檢視接下開的GC情況,部分截圖如下

     

   是不是很直觀,通過YGC這一列發現younggc次數沒有增加,但是通過FGC這一列看到fullgc的次數一直在增加,可怕的是老年代並沒有回收(通過O這列看出來)。

  這時候你是不是想起來了一個名詞:記憶體洩露。沒錯,接下來我們就需要分析哪裡出現了記憶體洩露。

 

  5:我們可以通過#jmap -dump:live,format=b,file=18400.dump 18400將這個程序當前的堆給dump下來。注意,這個檔案可以看成是堆的快照,所以當前堆有多大,dump下來差不多也有多大。我dump下來的差不多2G。

  有了這個檔案,我們需要分析,你可以用命令jhat分析,當然我們常用的是功能比較強大的圖形化工具,如JDK自帶的visualvm,也可以用第三方的JProfiler(我用的是這個),如果你用Eclipse,也可以安裝MAT外掛。這些工具都能分析堆dump檔案。

  需要注意的是,由於dump檔案可能比較大,所以所需分析工具的記憶體也比較大,最好在效能比較好的機器上進行分析。

  下面是我的JProfiler分析的部分截圖

  

 

   是不是很直觀,有個物件佔了97%的記憶體。那麼接下來需要分析這個物件在哪產生,在哪被引用。我這裡很明顯是這個LinkedList佔用了全部空間,那麼就去分析這個LinkedList裡面都存了些什麼,這些有可能需要結合你的程式碼,我就不細說了。

  我分析出來的是ElasticSearch的客戶端工具JestClient的非同步請求佇列太長了,整個List裡面的節點都是非同步請求資訊,大概生成了10多萬個。消費不及時又無法被回收,所以產生了記憶體洩露。(注意,使用執行緒池也有可能會出現這個情況)

 

  6:分析出了原因,那麼接下來就解決問題。因為我當時急著上線,不知道JestClient的非同步佇列長度怎麼配,就暫時把非同步改成了同步,暫時解決了這個問題。上線後檢視CPU,垃圾回收等情況確實恢復了正常。

  總的來說,上面的6步是一個完整的分析解決jvm虛擬機器記憶體洩露的流程,當然可能有不完善的地方,但大體思路是沒錯的。

  通過這篇文章,我們可以總結出以下幾點:

  1:如何分析Java服務佔用CPU過高的問題

  2:使用Java各種佇列的時候一定要關注佇列的長度,預防記憶體洩露。

  3:最好熟悉一下jvm的記憶體模型