1、Dump文件是什么
大家肯定知道我們java應用的對象的創建是由我們管,但是回收大多數是由jvm通過一定的算法來自動實現的,如:最少使用、不可達、新生代的復制清除等,也就是jvm會按照你現有對象占用的新生代或老年代的內存比例決定是否進行垃圾回收,每次垃圾回收都是需要STW的,但是當對象非正常產生的時候,jvm是回收不過來的,會造成不該有的對象直接將內存占滿甚至超過jvm設置大小,造成系統運行緩慢或者OOM。
當出現運行緩慢或者OOM的時候,我們就需要掌握jvm的內存狀態來查找我們系統變慢或者卡頓的原因。Java官方提供了生成jvm內存快照文件(dump文件)的工具jmap ,使用jmap即可生成。
同時,我們還可以通過dump文件分析系統運行時的情況。針對特定卡慢的方法進行優化。
1、Dump文件的生成
2.1、Linux生成dump文件
2.1.1、Linux上使用命令生成內存dump文件
1、執行 ps -ef | grep [應用名稱] --獲取 [pid]
2、執行 jmap -dump:format=b,file=filename [pid] 生成內存的dump文件,生成時間可能較久
2.1.2、Linux上使用命令生成線程dump文件
Jstack [pid] filename 就可以生成線程dump文件。
2.1.3、內存dump文件和線程dump的區別
內存dump文件着重當前應用的jvm的內存被哪些對象占用,線程dump文件着重顯示當前應用的各線程的運行情況,容易定位死鎖等問題。如圖。線程dump:

觀測線程dump文件的時候要特別注意:java.lang.Thread.State:Blocked,這種狀態可能出現了線程死鎖,然后造成系統卡頓。
所謂的線程死鎖:線程T1正在占用R1資源,需要獲取R2資源才能執行完成,然后釋放R1資源的鎖。線程T2正在占用R2資源,需要獲取R1資源才能執行完成,然后釋放R2資源的鎖。這就造成兩個線程相互等待對方釋放鎖資源,線程無法向前執行。解決方式:加1、鎖順序(線程按照一定的順序加鎖)
2、加鎖時限(線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己占有的鎖)
3、死鎖檢測
內存dump

稍后,我們對內存dump文件進項詳細介紹。
2.2、在出現OOM時自動保存dump文件
2、通過jvm參數--XX:+HeapDumpOnOutOfMemoryError可以讓JVM在出現內存溢出時候自動Dump出當前的內存轉儲快照。
3、Dump使用場景及問題分析思路
上線過程中可能會遇到各種各樣的問題,大多數的問題我們通過分析日志就可以得出結論,找到原因。
但是會出現一種問題我們很難通過日志找到原因:
系統剛上線各方面都很正常,但是由於某種操作觸發或者運行一段時間之后出現接口卡頓,接口響應緩慢超時甚至假死等現象。
這種情況的出現大多伴隨着系統硬件資源的使用率上升,如CPU和內存使用率突然急劇飆升,直接翻倍甚至占滿硬件資源。
如果出現系統剛上線應用、接口表現正常,運行一段時間之后出現卡頓緩慢現象,而日志又沒有表現出明顯錯誤,或直接出現OOM,我們需要使用這種排查思路:
首先查看系統硬件資源使用情況,使用top命令可清晰查看,如圖:

這是我們系統正常情況下 947 進程的使用情況,cpu使用率只有12.6 內存只有4.4,當系統出現問題的時候,我們再看系統的使用情況,如圖:

26608與上圖947為同一應用,發現cpu使用率直接飆升到100 % 內存使用情況翻倍,注意9.7是占用虛擬機的比例,但是我們單應用的jvm內存是不可以跟虛擬機的直接比較的,所以短時間內翻倍問題已經很大了。
這時候我們已經可以初步判定jvm的內存管理出現了問題,由於jvm的內存被不正常的對象大量搶占導致系統運行緩慢。
同時,我們數據中心的每台機器都是有zabbix監控(所有的數據中心都會有硬件資源監控系統,zabbix最為流行),我們同時可以提取zabbix的記錄來做驗證,如圖我們舉例一台:

我們可以很清晰的看到在10點到10點12之間內存和CPU(有公司信息就不放了)的情況發生了急劇的增長,但是這和進入我們系統的流量不是正常的比例,由此我們可以斷定我們的代碼的jvm的內存管理出現了問題,即大概率出現了內存溢出。
關於內存溢出:大家肯定知道我們java應用的對象的創建是由我們管,但是回收大多數是由jvm通過一定的算法來實現的,如:最少使用、不可達、新生代的復制清除等等,也就是jvm會按照你現有對象占用的新生代或老年代的內存比例決定是否進行垃圾回收,每次垃圾回收都是需要STW的,但是當對象非正常產生的時候,jvm是回收不過來的,會造成不該有的對象直接占滿、超過jvm設置大小,造成系統運行緩慢或者OOM。
如果出現了上述情況,我們必須、一定要保存dump文件!
因為dump是唯一可以反映java進程的內存情況的文件,也只有拿到當時的dump文件我們才可以分析出來造成我們本次上線的運行緩慢的原因,操作方式如圖(測試環境):

1、執行 ps -ef | grep [應用名稱] --獲取 [pid]
2、執行 jmap -dump:format=b,file=website.dump [pid] 生成dump文件,生成時間可能較久
先拿到該應用的pid,然后借助於jdk的jmap工具獲取dump文件。拿到dump文件之后就可以對應用的內存情況進行分析。
Dump文件需要借助特殊的工具進行分析,現在比較流行的是IBM Memory Analyzer和Eclipse Memory Analysis(MAT),我們用MAT來做演示。
這是我們系統運行正常時的截圖,如圖:

這是我們系統運行非正常的截圖,如圖:

在系統運行卡頓時,我們的char,String 的對象數量以及占用的內存空間發生了20倍的增長,同時我們自己的一個對象產生了202萬個,一個對象的正常創建是絕對不可能出現這么多的示例,所以可以確定使用該對象的地方的創建出現了問題,我們可以依此去代碼中查找問題。
同時Oracle的Driver對象也出現了不正常的增長,這將極大的占據數據庫資源,導致其他方法的數據庫連接出現問題。所以我們可以從代碼中尋找即使用了該對象,有連接了數據庫的地方。
同時除了這種運行變慢的,遇到OOM時,我們也可以通過對dump文件的分析查找到不合理占據內存的對象,從而定位到問題代碼。
另外,如果系統變慢,我們日志和內存dump文件都沒看到問題之后,我們就需要觀察Thread Dump文件,看是否有線程死鎖的問題。
正常的應用千篇一律,有趣的問題萬里挑一。
