jvm調優之內存泄漏分析

  一、前言

      我們都知道嚴重的內存泄漏會導致內存溢出,內存溢出最終會導致程序崩潰。前段日子,我幾乎被這個問題搞到內分泌失調,每個晚上都過得提心吊膽的,生怕一個微信或者電話過來說項目掛了。哎,說多了都是淚,直接進入主題吧。

二、問題描述

1.部署環境

      linux;

2.問題發現

        項目剛上線的時候,就出現響應慢的問題,甚至直接就502。首先想到的是數據庫的問題,然後在機器上安裝了mysql客戶端,連接線上的數據庫(連接命令:mysql -h(ip) -u(用戶名) -p(密碼)),隨便select了一張表,查詢效率都沒問題。這下沒轍了,去看一下日誌吧。發現都是broken pipe的異常(客戶端發送請求服務器長時間沒響應),再仔細一看,這個異常差不多都是在產生java.lang.OutOfMemoryError之後纔出現的。趕緊打開top命令一看,情況也是在預料之中,cpu飆到了百分之九十多。

     top:

       f 或者F 從當前顯示中添加或者刪除項目。

  o 或者O 改變顯示項目的順序。

  l 切換顯示平均負載和啓動時間信息。

  m 切換顯示內存信息。

  t 切換顯示進程和CPU狀態信息。

  c 切換顯示命令名稱和完整命令行。

  M 根據駐留內存大小進行排序。

  P 根據CPU使用百分比大小進行排序。

  T 根據時間/累計時間進行排序。

  3.內存泄漏分析過程

      根據top命令查出哪些線程在佔用cpu,導致cpu的使用率一直高居不下。

   (1)通過jps、ps或者其它方法,找到java的進程號PID;

   (2)top -p <pid>按H(當前進程的所有線程)找出cpu佔100%的線程號,因爲找出的線程號是二進制的,需要轉換爲十六進制;

發現有八個高cpu線程在做gc垃圾回收:

   (3)使用jstack -l <pid> |grep <轉換來的十六進制數>,查看線程正在做什麼。線程在dump中有三種狀態:RUNNABLE(執行中)、BLOCKED(被阻塞)、WAITING(等待中)。如果發現結果類似這種:"Concurrent Mark-Sweep GC Thread" prio=10 tid=0x0000000053293800 nid=0x*** runnable,說明jvm正在標記清除該線程,從而可以知道jvm正在忙着垃圾回收。由於jvm根據對象的存活週期不同將內存劃分爲幾個區。分爲年輕代和年老代,年輕代的對象大多都是小對象,jvm會頻繁進行回收,存活下來的對象會根據複製算法從from區到to區,只需要付出少量存活對象的複製成本就可以完成收集。而年老代的對象存活率都比較高,並沒有額外的空間幫它分配壓力,就使用“標記-整理”算法進行回收。

(4)使用jmap -heap <pid>查看堆內存情況,發現年老區的內存被撐到了99%,至此也不難理解爲什麼程序響應慢或者直接掛了,cpu都忙着垃圾回收,哪有時間去管其它事情;

(5)內存溢出也有可能是機器分配的內存不夠導致,於是我分配了8g的內存(xmx8g)給它,發現程序啓動沒多久,cpu又跑到百分之九十多了。發現根本就不是這個問題,才意識到垃圾沒有回收,加再大的內存也會被消耗掉,於是開始着手代碼優化了。

(6)代碼優化?從何下手呢。必須要找到哪些對象沒有被回收,哪些對象佔用的空間最多。於是,我從服務器dump了個堆文件下來分析(命令:jmap -dump:live,format=b,file=head.xx <pid>),這個文件可能有點大,拿下來的話,最好壓縮一下(命令:tar -czf dump.tar.gz dump.xx)。不拿下來的話,也可以用jhat命令進行分析詳細的就不說了。(注意:用這個的話,要看一下機器的內存是否夠,否則機器有可能會掛)

(7)文件拿下來後,需要用專門的內存分析工具。其中,有兩個工具可以用,一個是jdk1.6以上自帶的工具:jvisualvm,不過這個工具分析5G的堆文件相當吃力,等待的時間比較長,而且結果也不夠直觀。所以推薦使用第二種:eclipse的插件Memory Analyzer(下載:http://www.eclipse.org/downloads/download.php?file=/mat/1.8.1/rcp/MemoryAnalyzer-1.8.1.20180910-win32.win32.x86_64.zip)。如果打開的堆文件很大,需要修改一下MAT的啓動參數(memoryanalyzer.ini 中的xmx參數);

(8)打開後發現用戶表的對象佔了很大一部分沒有被回收,於是想到用戶表的很多字段沒有用的但是沒有去掉,是不是在別的地方用到了,導致了對象沒有被回收,於是把所有沒用的字段都去掉,更新。結果沒過一會又掛了,內心已到崩潰的邊緣。。。

(9)使用jmap -histo <pid>也可以查看推內存佔用情況,使用這個命令查看,發現還是用戶表的對象佔用了很多的內存,問題還沒解決......

(10)於是沒辦法了,自己寫個測試唄。你不是不回收嗎?我看你怎麼就不回收了,在用戶類裏面重寫了一下垃圾回收的finalize()方法,調用了一下JPA的保存方法,發現這個類果然沒有被回收。仔細研究發現這個類用到了hibernate的緩存技術,會不會是緩存引起的呢(緩存一種用來快速查找已經執行過的操作結果的數據結構,因此,如果一個操作執行需要比較多的資源並會多次被使用,通常做法是把常用的輸入數據的操作結果進行緩存,以便在下次調用該操作時使用緩存的數據。緩存通常都是以動態方式實現的,如果緩存設置不正確而大量使用緩存的話則會出現內存溢出的後果,因此需要將所使用的內存容量與檢索數據的速度加以平衡)。於是把緩存去掉,重新測了一遍,發現對象終於被回收了。

3.總結

      後來細細想了一下,十多萬的用戶,在線的人多了,由於緩存的原因,用戶對象一直沒有被回收,而且還一直堆積,才導致了內存的溢出。

     原以爲問題就此解決,結果第二天被告知,項目又掛了,心一下子又懸起來了。趕緊打開機器看了一下top信息,發現cpu正常 ,再看一下帶寬,原來是帶寬不足了......

     

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章