文章目標
當Java項目出現性能瓶頸的時候,通常先是對資源消耗做分析,包括CPU,文件IO,網絡IO,內存;之后再結合相應工具查找消耗主體的程序代碼。本文主要介紹系統資源消耗的分析過程,以及常用的Java線程分析方法。
CPU分析
在Linux中,CPU主要用於處理中斷、內核及用戶任務,優先級為:中斷>內核>用戶。在分析CPU消耗狀況的時候,需要了解以下三個概念。
上下文切換
每個CPU(或多核CPU的每個核心)在同一時間只能執行一個線程<不包括超線程CPU>,Linux采用搶占式調度。當線程執行到達一個時間片后,如果線程有IO阻塞或高優先級線程要執行的時候,Linux將執行線程切換,切換前先保存當前線程執行狀態(現場),並恢復待執行線程狀態,這個過程就叫做上下文切換。在Java應用中,文件IO、網絡IO、鎖等待、線程Sleep操作都會使該線程進行阻塞或睡眠狀態,從而觸發上下文切換。頻繁的上下文切換會造成內核占用較高的CPU,使得響應速度下降。
運行隊列
每個CPU核心都維護了一個可運行隊列,例如一個4核CPU,啟動8個線程,且8個線程都處於可運行狀態,平均分配情況下,每個核心的可運行隊列里就有2個線程。通常而言,系統的load是由CPU運行隊列決定的,假設以上狀態維持了1分鍾,則1分鍾內系統load就是2。運行隊列值越大,代表線程要消耗越長的時間才能執行完成。通常建議每個核心運行隊列為1-3個。
利用率
CPU利用率指在用戶進程,內核,中斷處理,IO等待以及空閑五個部分百分比,這五個值是用來分析CPU消耗情況的關鍵指標。Linux System and NetWork Performent Monitoring建議用戶進程/內核消耗比例為 65%-70% / 30%-35% 左右。
常用top, pidstat, sar, vmstat 1 分析占用情況,下圖是top示例
us:用戶進程處理占用百分比
sy:內核線程處理占用百分比
ni:被nice命令改變優先級的任務所占百分比
id:cpu空閑時間占用百分比
wa:在執行過程中等待IO所占百分比
hi:硬件中斷占用百分比
si:軟件中斷占用百分比
st:虛擬機偷取時間百分比
對Java應用而言,線程消耗主要體現在us, sy上:
us: 用戶進程處理占用百分比
us占用分析,需要依靠相關命令找出主體消耗線程ID(tid),然后轉化成十六進制(printf "%x\n" tid),再用 kill -3 java_pid或 jstack -l java_pid 命令dump出線程信息,通過之前的十六進制值在dump信息中找到nid相等的線程,即為消耗CPU的線程。采樣的時候要多做幾次,保證找到的是真實的消耗線程。
在Java應用中如果us占用過高,代表運行的應用程序消耗了大部分CPU,常見為線程一直處理可運行狀態(Runnable),並且無阻塞地執行循環,正則或復雜計算;也可能是每次請求都分配大量內存,導致頻繁GC甚至頻繁FullGC造成的,這時就需要依靠jvm工具查看了(jps, jmap, jstat等) 。
sy: 內核線程處理占用百分比
sy值過高表示Linux花費大量時間在線程切換上,Java造成原因通常是啟動大量線程,且多數線程處理不斷阻塞(如IO等待,鎖等待)和執行的狀態變化中,造成大量上下文切換。這時可通過 kill -3 java_pid或jstack -l java_pid 命令dump出線程信息,找出不斷切換線程執行狀態的原因(也可以通過TDA分析)。
如下使用 vmstat 1 查看上下文切換(cs)及sy占用
如果cs值很高的話,再使用 jstack -l java_pid 查看線程堆棧信息,通常可以發現大量線程處於TIMED_WAITING (on object monitor)與Runnable狀態轉化中,通過on object monitor可以找到鎖競爭激烈的代碼,從而找出上下文切換的原因。
文件IO分析
Linux在操作文件的時候,會將文件放入文件緩存區,直到內存不夠或系統要釋放內存給用戶進程使用時,才會交換出去。因此在查看內存狀態時經常發現可用(free)的物理內存不足,但cached用了很多,這是Linux提升文件IO速度的一種方法。這種情況下,如果物理內存足夠用,真正的文件IO只有寫文件和第一次讀的時候才會產生。
在Linux中文件IO主要通過 pidstat, iostat分析:
pidstat -d -p java_pid 1 3
KB_rd/s 表示每秒讀取的KB數, KB_wr/s表示每秒寫入的KB數, 還可以加入-t參數顯示具體的線程信息。
iostat
iostat只能看到整個系統的文件IO,不能查看具體進程消耗情況。Device表示設備卷標名或分區名,tps是每秒的IO請求,是IO消耗關鍵指標;Blk_read/s表示每秒讀的塊數量,Blk_wrtn/s表示每秒寫的塊數量;Blk_read, Blk_wrtn表示總共讀寫的塊數量;當%iowait占用很高的時候,就要關注IO消耗狀況了,這時可以使用 iostat -x 觀察:
r/s, w/s 表示每秒讀寫的請求數, await表示平均每次IO操作的等待時間,avgqu-sz表示等待請求的隊列的平均長度,svctm表示平均每次設備執行IO操作的時間,util表示一秒之中有百分之幾用於IO操作。
在Java應用中造成文件IO消耗嚴重的原因,通常是多個線程進行大量寫入操作(如頻繁寫入日志文件)。這時可以通過pidstat或iostat結合jstack線程信息,找到消耗主體程序。
網絡IO分析
在分布式Java應用中,網絡IO的消耗是非常值得關注的,尤其注意網卡中斷是不是均勻地分配到各CPU上(cat /proc/interrupts)。Linux使用sar分析網絡IO消耗情況:
sar -n ALL 1 2
主要觀注接包(rxpck/s),發包(txpck/s),接包失敗(rxerr/s),發包失敗(txerr/s),丟包(rxdrop/s),Socket信息(tcpsck , udpsck)。
由於無法觀察具體進程的網絡IO消耗,在網絡IO消耗高時,只能線程dump,通常這些線程都在進行網絡讀寫操作。在Java網絡通信中,通常將對象序列化為字節流發送,反序列化生成對象。
內存分析
從Java應用角度上看,內存可分為兩部分,即JVM內存與非JVM內存。在JVM中內存消耗主要體現在堆內存上,內存消耗過高會導致頻繁GC甚至FullGC,CPU占用高,可以通過jmap, jstat, mat, visualvm等工具跟蹤內存消耗情況;生產環境下,通常將 -Xms 和 -Xmx調整為相同的值,避免運行時不斷申請內存。非JVM內存通常只有在創建線程或使用DirectByteBuffer時才會產生,最值得關注的是swap的消耗與物理內存的消耗。
vmstat
swpd表示虛擬內存已使用的部分(kb),free空閑物理內存,buff表示用於緩沖的內存,cache表示用於作為緩存的內存。swap下的si表示每秒從disk讀到內存的數據量,so每秒從內存寫入disk的數據量。swpd過高表示物理內存不夠用,系統需要頻繁從虛擬內存與disk交換數據,嚴重影響系統的性能。
sar -r 2 5
通過sar工具可以看到內存占用,空閑,buff, cache的情況。當物理內存空閑時,Linux會使用一部分內存用於buffer以及cache,以提高系統運行效率。因此可認為系統可用物理內存為 kbmemfree + kbbuffers + kbcached。
此外還可以使用top, pidstat -r -p [pid][interval][times]
pidstat -r -p 2448 1 5
參考資料
中斷:http://blog.csdn.net/pxz_002/article/details/7327668
CPU占用分析:http://www.cnblogs.com/yjf512/p/3383915.html
林昊:分布式Java應用
JVM內存分析:http://my.oschina.net/feichexia/blog/196575
https://wenku.baidu.com/view/c7c38dbe4b35eefdc8d333a8.html