Java程序員也應該知道的係統知識係列之CPU
作者:林昊
去年在排查很多java應用的問題時候,看到一些現象是程序員對自己寫完的程序所運行的環境了解很少,導致排查問題的時候會比較折騰,因此想到了寫這個係列的文章,程序要提供功能給最終用戶使用,代碼隻是其中的一個部分,它還需要依賴jvm、os、服務器硬件、網絡、負載均衡等等來共同完成,在這個係列的文章中,將重點關注除jvm外的幾個部分,更多的也隻是一個科普作用,由於os我使用的都是linux,這個係列的文章中講到的os也都默認就是linux,這是這個係列的第一篇:CPU。
Java程序在運行時和CPU的關係是怎麼樣的,是怎麼去使用和更充分的使用CPU,以及我們有什麼辦法能夠來控製CPU呢,這是這篇文章中關注的幾個重點,如果還有你想關注的,但這裏也沒提到的,以及本文中一些錯誤的地方,都歡迎回複下,:)
首先需要知道程序運行的機器的CPU狀況,至少得了解下有幾個CPU,CPU的型號,是不是開啟了超線程(Hyper Thread)等,這些信息會對程序的執行性能有不小的影響(一個好一點的型號帶來的性能和吞吐量提升是非常明顯的,所以在看到各種不提供硬件環境說明的超NB的性能測試報告時,都不需要太驚訝),這些可以通過在機器上cat /proc/cpuinfo來獲取,如果是虛擬機的話看到的信息可能會有些奇怪,例如有些虛擬機看到的會是virtual cpu等,這種就隻能到宿主機上去查看了。
在/proc/cpuinfo裏可以看到具體的cpu數量,cpu型號,HT是否開啟可以根據physical id和core id來判斷,如果看到兩個processor的這兩個id是一致的,那說明是開啟了HT的,開啟HT對於支撐高並發的場景而言,基本是有利無弊,一般來說可以提升60%左右(但做不到翻倍的提升,也不太可能做到)。
在cpuinfo中還會看到有flags這欄,感興趣的可以多去了解下,有些會對應用的運行性能有很大的影響,當然,這些也可以通過cpu型號多進行一些了解。
在了解了機器的CPU硬件情況外,可以來看看Java程序執行的過程中和CPU的關係,Java程序是一個進程,多線程的方式執行,Java線程和OS線程可以是一對一的關係,也可以是多對一的關係,多對一這個更多的是因為以前的linux對多線程支持不好,所以可以認為現在通常情況下Java線程和OS線程都是一對一的關係。
既然是一對一的關係,因此線程具體獲取CPU的執行時間片也由OS來控製(虛擬機的話會更複雜一點,這個就得講到虛擬化的一些知識點了,這個之後再另寫吧),默認情況下啟動的進程可以使用機器上所有的CPU,但也可以通過taskset命令來控製一個進程可使用的CPU,甚至可以更精細的去控製到某個線程使用的CPU(因為在linux上線程在某種程度上也可以認為是“進程”)。
linux是分時調度的操作係統,處於Runnable狀態的線程將根據時間片獲得調度(具體os是怎麼去調度的,怎麼去保障多個core的cpu是比較均衡的,建議參看相應的操作係統方麵的文章或書),Java程序在執行過程中根據執行的代碼將進入不同的線程狀態,具體可通過java自帶的jstack來查看線程的狀態,但jstack看到的線程狀態不一定就是對的,例如很多時候會看到epollWait這樣的代碼對應的線程一直處於Runnable,其實更多的是因為jstack默認情況下隻能看到Java棧上的狀況,但Java代碼執行時很多時候需要依賴native的代碼,所以這種時候通常看到的線程狀態就有可能是不對的,在比較新的java版本裏,是可以通過jstack -m來直接把native stack和Java stack合在一起的,這種情況下就會準確很多。
因此可以認為在Java程序方麵更多的是通過改變線程的狀態來一定程度上幹涉對cpu的使用,例如像disruptor這些號稱更實時的調度,是靠極短的wait時間或直接的while(true)來保證線程大部分時候處於runnable,同樣像sleep等也是很多代碼用來主動釋放cpu使用的方法。
如果想運行的線程獲取更多的執行機會,看起來Java中提供的設置線程優先級的方法是比較合用的,但由於這個優先級的生效與否和os有很大的關係,並且不一定合理,所以不用比較好,對於實在是對應用運行很關鍵的線程,例如在通信密集的java程序中,nio的io處理線程可能非常關鍵,但通常nio的io處理線程數會很少,而其他線程數很多,如果都處於分時調度的情況下,那麼有可能會導致有些情況下io處理線程得到的執行機會不夠,對於這種特殊情況可以采用taskset來解決,可能有些同學看到這會想到那jvm是怎麼讓它的一些線程享有高的執行權限的呢,例如gc線程等,這個在之後講內存的時候會講到。
在運行的過程中,可以通過top -H來查看運行的java應用的每個線程耗cpu的狀況,如果你看到的現象是消耗cpu的線程不斷的變化,且每個線程消耗的cpu都不多,那說明應用的運行狀況是不錯的(當然,這種情況通常可能也有優化空間),反之如果總是看到有個別線程消耗的cpu比例比較高,就需要查查是什麼原因了,可通過看到的線程id做十六進製轉化,然後對應到jstack出來的線程信息的nid上,看看堆棧具體做的動作,更好的辦法則是通過perf這樣的工具來查看,但默認版本的perf是無法統計經過c2編譯優化後的java代碼的cpu消耗的,會統計到?裏,所以對查問題幫助就會顯得小了很多,這個要支持的話得修改perf和jvm才行。
對於Java程序而言,更多的需要考慮的是如何能充分的發揮cpu,而對於多數Java應用而言,通常其他硬件資源都不太會成為瓶頸,所以寫的Java程序在隨著並發量上升的情況下,應該盡可能去做到跑滿cpu。
top後按數字1可以看到每個cpu core的消耗狀況,主要會有us sy ni id wa hi si st這幾個指標,分別對應用戶態的消耗 係統內核的消耗 調過ni值的進程的cpu us的消耗 cpu空閑 iowait的消耗 硬中斷響應的消耗 軟中斷響應的消耗 被其他虛擬機借用的消耗,要跑滿cpu,不是其中的一個cpu core的id這個值接近0,而應該是所有的cpu core的id都接近0才是合理的,並且對於Java程序而言,應該盡可能讓us這個值跑的比較高。
有些應用可能會出現即使並發量上漲,但cpu的id也一直降不太下去或者說us上不來,在排查了不是其他硬件資源的瓶頸問題外,通常可能會是以下的一些原因:
1.處理的線程數不夠,多數請求處理的過程中並不是全部耗cpu的,例如鎖等待、io事件等待等等,這種時候線程會進入blocked或waiting等狀態,如運行的線程不夠多,就會導致cpu us上不去,現在的java應用從請求進來一直到真正的處理,通常會經曆多個線程池,因此在查這類問題的時候一定要清楚整個處理過程,然後看看每個過程的線程數是不是夠用,判斷是否夠用還是比較容易的,jstack如果看到所有的線程都是在執行應用相應的代碼,而不是線程池類的wait等時,那基本就說明線程數可能不夠用了,這種情況下可以嚐試增加線程池的線程數試試,看看cpu的利用率能否上去,但這個地方不太好折騰的是線程數到底設置為多大合適,太小的話cpu不能充分使用,太大則可能造成全部運行起來後存活的線程比較多,導致占用的Java堆內存也增加,從而形成頻繁的gc,問題更嚴重,所以一直以來線程池的線程數設置多大都是個痛苦的問題,基本上得靠經驗以及在不同硬件情況下的測試來設置,動態的貌似一直沒找到什麼好的辦法,如果大家有知道的,拜托推薦下,:)
2.另外一種狀況是線程池的線程數加大了,但仍然沒什麼作用,這種通常有可能是處理的過程中各並發線程處理過程中串行的部分耗時較長,例如需要同一把鎖等場景,這種通過jstack通常也能看出,需要做的則是引入各種高並發的技巧,尤其是無鎖數據結構或鎖粒度的控製來解決。
通常情況下,如果能把上麵兩問題都解決好,那麼基本上是可以在隨著並發量上升的情況下,把cpu也充分利用起來的,如果還碰到了詭異的其中某個cpu的消耗一直比其他的高不少而成為瓶頸的現象,則有可能是網卡中斷等的處理造成的,這種可以看看我之前寫的一篇關於網卡中斷的文章。
最後更新:2017-04-03 07:57:06
上一篇:
S(tuple)類及可選(Optional)類型型
下一篇:
Ubuntu 上 hi3531 交叉編譯環境 arm-hisiv100nptl-linux 搭建過程
《淘寶店鋪 大數據營銷+SEO+爆款打造 一冊通》一一1.3 客單價
關於selenium自動化測試數據的管理---測試用例管理---jxl,POI
C++編程規範之11:隱藏信息
ASP.NET程序中常用編程代碼
網頁標題亂碼的解決辦法
使用hexo在GitHub上搭建個人博客網站
PostgreSQL 10 GIN索引 鎖優化
《大數據算法》一第2章 時間亞線性算法
???????????????Elasticsearch????????????2????????????2.5.2???????????????-??????-????????????-?????????
數據安全隱私保護九大解決之道