技術干貨實戰(5)- JVM 性能監控工具之jstack、jstat等命令

作者: 修羅debug
版權聲明:本文為博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處鏈接和本聲明。


日常應用巡檢期間,突然發現線上應用服務器CPU飆高、內存占用過高等情況時,傳統的做法是不管三七二十一,直接重啟線上應用,一了百了,這種方式雖然有時候可以解決一時的問題,但是卻會讓工程師錯失排錯、揪出問題根源的機會;本文將介紹出現上述等情況時,如何對整個應用服務器的整體性能、Java應用服務進行監控、排查以及定位,其中的jvm命令包括:jstack、jstat等等


在現實企業級Java應用開發、維護中,有時候我們會碰到下面這些問題:

1OOM,即OutOfMemoryError,內存溢出/內存不足;

2)線程死鎖 或者  鎖爭用(Lock Contention

3Java進程消耗CPU過高,打開云服務器的性能指標監控直方圖時會發現CPU一直居高不下 ......


這些問題在日常開發、維護中可能被很多人忽視(比如有的人遇到上面的問題只是暴力重啟或者調大內存,而不會深究其問題根源),但說實在的,倘若能夠理解并解決這些問題,那將大大增強Java開發者的進階技能;

本文將以debug新發布的新課 “Java核心技術-典型案例與面試實戰系列二(SpringBoot2.0+企業真實案例)”中提及的、集群部署下的“權限管理平臺”項目為案例,部署到Linux ECS Centos7上,并采用JVM相應的命令進行監控查看,訪問地址為:http://history.huicairj.com/

一、jstack

1)作用:查看一個java進程內 線程占用的堆棧內存信息

2)語法格式:   

jstack [option]  pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip

參數額外說明:

-l long listings,會打印出額外的鎖信息,在發生死鎖時可以用jstack -l pid來觀察鎖持有情況-m mixed mode,不僅會輸出Java堆棧信息,還會輸出C/C++堆棧信息(比如Native方法),如下圖所示為 進程pid 13884 對應的項目 “權限管理平臺”內各個線程持有鎖的情況,輸入命令后回車,并移動到輸出的信息的最下面即可得到::



目前沒有看到某個線程獨占鎖的情況,即None,也沒有出現某個線程正在等待獲取某個資源Waiting No Object;

一般如果出現死鎖的話,會伴隨著兩個現象:A.某個線程等待獲取某個資源的鎖,即Waiting to lock monitor…   B.這個資源卻被另外的線程所持有,即which is held by “Thread-xxxx”,如下圖所示為多年前debug曾經的一次日常線上應用巡檢時出現的情況:


而很明顯兩個線程在互相爭奪對方的持有的資源,僵持不下,因此也就出現了:死鎖,即dead lock

順提一下,檢測死鎖還可以利用JVM自帶的jconsole工具進行查看(在成功安裝jdk時有個bin目錄,里面的jconsole.exe就是),如下圖所示:



3)命令實操:言歸正傳,jstack可以定位到線程堆棧,根據堆棧信息我們可以定位到具體的代碼,所以它在JVM性能調優與監控中使用得非常多;

 

下面以“權限管理平臺”對應的進程:13884為例,查看其線程中最耗費CPUJava線程并定位堆棧信息,用到的命令有top、printf、jstack、grep

已經確定了進程id 13884 后,接下來查看該進程內部最耗費CPU的線程,命令為:   

top -Hp 13884

如下圖所示:



TIME那一列就是各個線程耗費的CPU時間,CPU時間最長的是線程ID13893的線程,然后執行以下命令:   

printf "%x" 13893


得到13893的十六進制值為3645,在下面會用到;緊接著輪到jstack上場了,用于輸出進程13893的堆棧信息,然后根據線程ID的十六進制值grep,如下:

jstack 13884 | grep 3654


回車后出現如下的結果:

"C2 CompilerThread0" #48 daemon prio=0 os_prio=0 tid=0x00007f45f0b80000 nid=0x188 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE


如果是出現這種結果 且 線上CPU真的飆高的話,那應該考慮關閉下C2編譯器,即只需要在java -jar xx.jar 啟動命令中,加下jvm的參數即可:-XX:-TieredCompilation –server ,其深層次的含義為:關閉JIT分層編譯

這玩意比較深奧,感興趣的小伙伴可以網上搜羅一番資料自行學習學習;

另外,有些時候執行 jstack 13884 | grep 3654 命令得到的結果可能是某個 類 在等待執行某個方法,那么這個時候確實代碼寫得有問題了,可以根據返回的結果提示找到具體的類方法代碼即可!  


二、jstat

1)作用: jstatJVM統計監測工具)可用于查看應用所占堆內存各個區的情況以及GC的情況,在實際應用期間也是很廣泛的!

2)語法格式:

jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]


vmidJava虛擬機的ID,在Linux/Unix系統上一般就是進程ID,interval是采樣時間間隔,count是采樣數目

在命令實操之前,首先得給大家介紹下JVM的內存結構,因為待會兒在解釋 執行 jstat 命令后打印出來的結果時需要用到;如下圖所示Java虛擬機給一個應用分配的運行時的內存結構,如下圖所示:


各控制參數的含義如下所示:   

-Xms設置堆的初始(最?。┛臻g大小。

-Xmx設置堆的最大空間大小。

-XX:NewSize設置新生代初始(最?。┛臻g大小。

-XX:MaxNewSize設置新生代最大空間大小。

-XX:PermSize設置永久代(方法區)最小空間大小。

-XX:MaxPermSize設置永久代(方法區)最大空間大小。

-Xss設置每個線程的堆棧大小。

(注:方法區在JDK1.7的時候叫做永久代,到JDK1.8之后廢棄了永久代改為元空間(meta space))

而堆內存head space那一塊是我們最為關心的,因為幾乎所有的對象實例、數組都存放在java堆里,它也是GC回收重點關注的地方,其內存結構我們單獨抽出來,如下圖所示:


 

不難看出:

堆內存 = 年輕代 + 年老代 + 永久代  (這個有點爭議)
年輕代 = Eden區(Eden) + 兩個Survivor區(From和To),即S0 和 S1

默認Eden:from :to = 8:1:1

3)命令實操:如下圖輸出的GC信息中,采樣時間間隔為2s,共采樣60次的結果:


 

上圖輸出的信息中各列的含義如下所示(以下的 容量 字段信息 單位為:字節 ):   

S0C、S1C、S0U、S1U:Survivor 0/1區分配的容量(Capacity)和使用量(Used)

EC、EU:Eden區分配的容量和使用量

OC、OU:年老代容量和使用量

PC、PU:永久代容量和使用量

YGC、YGT:年輕代GC次數和GC耗時

FGC、FGCT:Full GC次數和Full GC耗時

GCT:GC總耗時


在上圖中我們可以看到,每隔2s采樣一次,共采樣次數為60次,在此期間發生了3次的YGC (因為debug的這臺測試服務器本身內存不高:2g 而已),其發生的過程在上圖中已經都標注出來了,諸位可以自行看看,下面再簡單地總結下GC的相關概念以及觸發GC的時機和過程:

1)基本概念

A. YGC :對新生代堆內存進行垃圾回收,即GC,頻率比較高,因為大部分對象的存活壽命較短,在新生代里被回收,性能耗費較小。

B. FGC :全范圍堆內存的GC,默認堆空間使用到達80%(可調整)的時候會觸發FGC,以我們生產環境為例,一般比較少會觸發FGC,有時30天或幾個月會有一次;


2)觸發GC的時機以及GC過程

A.YGC的時機Eden空間不足

像上圖所展示的,Eden空間已經不足以支撐下次的對象內存分配了,因此會觸發YGC,其GC過程比較簡單:標記Eden區和其中一個S區,如S1仍然存活的對象,然后Copy將其搬遷至另一個S區,如S0 From To命令的由來),同時釋放EdenS1內存空間,那些存活的對象年齡 +1,YGC次數 +1;

隨著系統的運行,會發現Eden空間再次不足,此時再次觸發YGC,其過程也是一個道理,從Eden區和S0區標記出仍然存活的對象并將其CopyS1區,釋放空間,對象年齡+1,YGC+1,如此循環反復,當對象年齡達到15時(默認是15,可以調整設置),即會開始將那些仍然存活的對象搬遷拷貝至年老代Old(以上過程是以標記-清除復制算法為例)


B.FGC的時機Old空間不足;Perm空間不足;顯示調用System.gc() ,包括RMI等的定時觸發;YGC時的悲觀策略;dump live的內存信息時(jmap dump:live)。


而對于FGC觸發的時機則比較多,但常見無非是:Old空間不足 或 Perm空間不足,其GC過程比較復雜(取決于采用什么GC算法 和 垃圾回收器,經典的當然是:分代收集算法),在這里debug就不做過多介紹了,感興趣的小伙伴可以網上搜羅一番,參考鏈接:https://www.cnblogs.com/bigbaby/p/12348968.html


總結:

1)代碼下載:文中涉及到的“權限管理平臺”項目源碼數據庫 可以通過關注微信公眾號:程序員實戰基地(掃描網站底部的微信公眾號即可),回復數字: 101 ,即可下載 !

我是debug,一個相信技術改變生活、技術成就夢想 的攻城獅;如果本文對你有幫助,請關注公眾號,并動動手指收藏、點贊、以及轉發哦?。?!