1. 程式人生 > 其它 >Linux下JVM常見問題處理

Linux下JVM常見問題處理

一、背景分析

線上故障主要會包括 CPU、記憶體磁碟以及網路問題,而大多數故障可能會包含不止一個層面的問題,所以進行排查時候儘量四個方面依次排查一遍。基本上出問題就是 df、free、top,然後依次 使用jstack、jmap,具體問題具體分析。

二、CPU分析

一般來講我們首先會排查 CPU 方面的問題。原因包括業務邏輯問題(死迴圈)、頻繁 GC以及上下文切換過多。而最常見的往往是業務邏輯(或者框架邏輯)導致的,可以使用 jstack 來分析對應的執行緒棧情況。

1.使用 jstack 分析執行緒棧

先用 ps 命令找到對應程序的 pid有好幾個目標程序,可以先 top 找到cpu較高的程序

接著top -H -p <pid> 找到 CPU 使用率比較高的一些執行緒

然後將佔用最高的 pid 轉換為 16 進位制printf '%x\n' <pid>得到 nid

接著直接在 jstack 中找到相應的堆疊資訊

jstack <pid>|grep '<nid>' -C5 –color

利用jstack生成虛擬機器中所有執行緒的快照

jstack -l {pid} > {path}

cat {path}|grep '790D' -C 5

執行緒快照格式都是統一的,我們以一個執行緒快照簡單說明下

"http-nio-9001-exec-269" #536 daemon prio=5 os_prio=0 tid=0x00007f04700eb800 nid=0x790d runnable [0x00007f0435cb4000]

java.lang.Thread.State: RUNNABLE

"http-nio-9001-exec-269"執行緒名稱

#536執行緒編號

daemon守護執行緒

prio=5執行緒的優先順序

os_prio=0系統級別的執行緒優先順序

tid=0x00007f04700eb800執行緒id

nid=0x790d native執行緒的id

runnable [0x00007f0435cb4000]執行緒當前狀態

對整個 jstack 檔案進行分析,通常我們會比較關注 WAITING 和 TIMED_WAITING 的部分,如果 WAITING 之類的特別多,那麼多半是有問題。

cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c

2.頻繁 GC

先確定下 gc 是不是太頻繁,使用jstat -gc pid 1000命令來對 gc 分代變化情況進行觀察,1000 表示取樣間隔(ms)

jstat -gc pid 1000

如果看到 gc 比較頻繁,再針對 gc 方面做進一步分析。

3.上下文切換

針對頻繁上下文問題,我們可以使用vmstat命令來進行檢視cs(context switch)一列則代表了上下文切換的次數。

如果對特定的 pid 進行監控可以使用 pidstat -w pid命令cswch 和 nvcswch 表示自願及非自願切換。

pidstat -w <pid>

4.分析過程

(1)找到CPU使用率最高的程序

(2)找到該程序中CPU使用率最高的執行緒

(3)利用jstack生成該程序下所有執行緒快照

(4)在快照中找到對應的執行緒分析問題

三、磁碟

首先是磁碟空間方面,使用df -hl來檢視檔案系統狀態

df -hl

更多時候磁碟問題還是效能上的問題可以通過 iostat -d -k -x來進行分析

iostat -d -k -x

最後一列%util可以看到每塊磁碟寫入的程度,而rrqpm/s以及wrqm/s分別表示讀寫速度,一般就能定位到具體哪塊磁碟出現問題了。

四、記憶體

主要包括 OOM、GC 問題和堆外記憶體。一般來先用free命令先來檢查記憶體的各種情況。

free

或者

free -h

1.堆內記憶體

記憶體問題大多還都是堆內記憶體問題。表象上主要分為 OOM 和 Stack Overflow

(1)OOM

JMV 中的記憶體不足,OOM 大致可以分為以下幾種:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

沒有足夠的記憶體空間給執行緒分配 Java 棧,基本上還是執行緒池程式碼寫的有問題,比如說忘記 shutdown,JVM 方面可以通過指定Xss來減少單個 thread stack 的大小。另外也可以在系統層面增大 os 對執行緒的限制

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

堆的記憶體佔用已經達到-Xmx 設定的最大值。解決思路仍然是先應該在程式碼中找,懷疑存在記憶體洩漏,通過 jstack 和 jmap 去定位問題。如果一切都正常需要通過調整Xmx的值來擴大記憶體。

Caused by: java.lang.OutOfMemoryError: Meta space

元資料區的記憶體佔用已經達到XX:MaxMetaspaceSize設定的最大值,解決思路仍然是先應該在程式碼中找,懷疑存在記憶體洩漏,通過 jstack 和 jmap 去定位問題引數方面可以通過XX:MaxPermSize來進行調整。

java.lang.StackOverflowError

棧溢位丟擲java.lang.StackOverflowError錯誤,出現此種情況是因為方法執行的時候,請求新建棧幀時,棧所剩空間小於戰幀所需空間執行緒棧需要的記憶體大於 Xss 值)。常見於通過深度遞迴呼叫方法,不停的產生棧幀,一直把棧空間堆滿,直到丟擲異常

Exception in thread "main" java.lang.StackOverflowError

(2)使用 JMAP 定位程式碼記憶體洩漏

關於 OOM 和 Stack Overflo 的程式碼排查方面,我們一般使用 JMAP來匯出 dump 檔案

jmap -dump:format=b,file=filename <pid>

MAT(Eclipse Memory Analysis Tools)

mat獨立版下載地址http://www.eclipse.org/mat/downloads.php

注:安裝完成後,為了更有效率的使用MAT,可以配置一些環境引數。因為通常而言,分析一個堆轉儲檔案需要消耗很多的堆空間,為了保證分析的效率和效能,建議分配給MAT儘可能多的記憶體資源。可以採用如下兩種方式來分配記憶體更多的記憶體資源給MAT。

第一修改啟動引數MemoryAnalyzer.exe-vmargs -Xmx4g

第二編輯檔案MemoryAnalyzer.ini,在裡面新增類似資訊-vmargs– Xmx4g。

1. MemoryAnalyzer.ini中的引數一般預設為-vmargs– Xmx1024m,這就夠用了。假如機器的記憶體不大,改大該引數的值,會導致MemoryAnalyzer啟動時,報錯:Failed to create the Java Virtual Machine。

2.當匯出的dump檔案的大小大於配置的1024m(說明1中,提到的配置:-vmargs– Xmx1024m),MAT輸出分析報告的時候,會報錯:An internal error occurred during: "Parsing heap dump from XXX”。適當調大說明1中的引數即可。

mat(Eclipse Memory Analysis Tools)匯入 dump 檔案進行分析

記憶體洩漏問題一般直接選 Leak Suspects 即可,mat 給出了記憶體洩漏的建議。執行緒相關的問題可以選擇 thread overview 進行分析。

IBM記憶體分析工具 ha457.jar

IBM HeapAnalyzer下載地址https://www.ibm.com/support/pages/ibm-heapanalyzer

生產環境自動dump出oom資訊的phrof檔案

使用啟動引數 -XX:+HeapDumpOnOutOfMemoryError

匯出地址:-XX:+HeapDumpPath={path}

2.GC 問題和執行緒

GC問題除了影響 CPU 也會影響記憶體,排查思路也是一致的。一般先使用 jstat 來檢視分代變化情況,比如 youngGC 或者 fullGC 次數是不是太多;EU、OU 等指標增長是不是異常等。

執行緒的話太多而且不被及時 gc 也會引發 oom,大部分是unable to create new native thread。

3.堆外記憶體

堆外記憶體溢位表現就是物理常駐記憶體增長快,報錯的話視使用方式都不確定,如果由於使用 Netty 導致的,那錯誤日誌裡可能會出現OutOfDirectMemoryError錯誤,如果直接是 DirectByteBuffer,那會報OutOfMemoryError: Direct buffer memory。

堆外記憶體溢位往往是和 NIO 的使用相關,一般我們先通過 pmap 來檢視下程序佔用的記憶體情況pmap -x pid | sort -rn -k3 | head -30,意思是檢視對應 pid 倒序前 30 大的記憶體段。這邊可以再一段時間後再跑一次命令看看記憶體增長情況,或者和正常機器比較可疑的記憶體段在哪裡。

關鍵還是要看錯誤日誌棧,找到可疑的物件,搞清楚回收機制,然後去分析對應的物件。比如 DirectByteBuffer 分配記憶體的話,是需要 full GC 或者手動 system.gc 來進行回收的。那麼其實我們可以跟蹤一下 DirectByteBuffer 物件的記憶體情況,通過jmap -histo:live pid手動觸發 fullGC 來看看堆外記憶體有沒有被回收。如果被回收了,那麼大概率是堆外記憶體本身分配的太小了,通過-XX:MaxDirectMemorySize進行調整。如果沒有什麼變化,那就要使用 jmap 去分析那些不能被 gc 的物件,以及和 DirectByteBuffer 之間的引用關係了。

五、GC 問題

堆內記憶體洩漏總是和 GC 異常相伴。不過 GC 問題不只是和記憶體問題相關,還有可能引起 CPU 負載、網路問題等,只是相對來說和記憶體聯絡緊密些。

在啟動引數中加上-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps來開啟 GC 日誌。GC日誌能大致推斷出 youngGC 與 fullGC 是否過於頻繁或者耗時過長

1.youngGC 過頻繁

youngGC 頻繁一般是短週期小物件較多,先考慮是不是新生代設定的太小了,看能否通過調整-Xmn、-XX:SurvivorRatio 等引數設定來解決問題。如果引數正常,但是 young gc 頻率還是太高,就需要使用 Jmap 和 MAT 對 dump 檔案進行進一步排查了。

-Xmn2G設定年輕代大小為2G

-XX:SurvivorRatio=8 定義了新生代中Eden區域和Survivor區域的比例,預設為8,也就是說Eden佔新生代的8/10,From倖存區和To倖存區各佔新生代的1/10

2.youngGC 耗時過長

耗時過長問題就要看 GC 日誌裡耗時耗在哪一塊了。

3.觸發 fullGC

(1)直接呼叫 System.gc() 時(呼叫後並不會立即發生 fullGC,後面會在某個時間點發生)

(2)老年代的可用空間不足時

(3)方法區空間不足時,或 Metaspace Space 使用達到 MetaspaceSize 但未達到 MaxMetaspaceSize 閾值;大多情況下擴容都會觸發

(4)通過Minor GC後進入老年代的平均大小大於老年代的可用記憶體時。由 Eden 區、From Survior 區向 To Survior 區複製時,物件大小大於 To Survior 區可用記憶體,則把該物件轉存到老年代

(5)執行 jmap -histo:live 或者 jmap -dump:live;

分析過程

(1)找到記憶體使用率最高的程序

(2)利用jmap生成該程序的堆轉儲快照

(3)利用轉儲快照分析工具分析問題

六、總結

1.JVM 常用命令

jps:列出正在執行的虛擬機器程序

jstat:監視虛擬機器各種執行狀態資訊,可以顯示虛擬機器程序中的類裝載、記憶體、垃圾收集、JIT編譯等執行資料

jinfo:實時檢視和調整虛擬機器各項引數

jmap:生成堆轉儲快照,也可以查詢 finalize 執行佇列、Java 堆和永久代的詳細資訊

jstack:生成虛擬機器當前時刻的執行緒快照

jhat:虛擬機器堆轉儲快照分析工具

jmap 搭配使用,分析 jmap 生成的堆轉儲快照,與 MAT 的作用類似

2.排查步驟

1、先找到對應的程序:PID

2、生成執行緒快照stack(或堆轉儲快照:hprof)

3、分析快照(或堆轉儲快照),定位問題

3.記憶體洩露、記憶體溢位和 CPU 100% 關係

4.常用 JVM 效能檢測工具

Eclipse Memory Analyer、JProfile、JProbe Profiler、JVisualVM、JConsole、Plumbr





沒有高深的知識,沒有進階的技巧,萬丈高樓平地起~!