打造高效能Java應用需掌握的5大知識

Bugtags發表於2016-04-07

這篇文章節選自《java performance》,對java效能比較關心的同學大概都知道這本書,效能這個東西可能是很多同學在日常寫java code的時候很少去關心的,但是在我們寫code的過程中確又時時離不開對程式效能的影響,小到我們使用位運算來實現算術運算,大到我們對JAVA程式碼的總體架構設計,效能其實離我們很近。本片文章主要提到幾個點,主要是在效能領域我們比較關注的一些問題,並且是有啟發性的,如果同學對效能較感興趣,那麼我們可以一起深入研究各個點。

對於效能調優,通常會有三個步驟:1,效能監控;2,效能剖析;3,效能調優

我們對於作業系統的效能關注主要在下面幾個點上:CPU利用率、CPU排程執行佇列、記憶體利用率、網路I/O、磁碟I/O。

1.CPU利用率

對於一個應用來說,為了讓應用達到最好的效能和可擴充套件性,我們不僅僅要充分利用CPU週期內可用的部分,而且要讓這部分CPU的使用更有價值,而不是浪費。能夠讓CPU的週期利用的更充分對於多執行緒應用執行在多處理器和多核系統上至很有挑戰性的。另外,當CPU達到飽和狀態的時候並不能說明CPU的效能和伸縮性已經達到了最佳的狀態。為了區分應用是如何利用CPU資源的,我們必須從作業系統級別來檢測。在很多作業系統上,CPU的利用率統計報告通常包括使用者和系統或核心對作業系統的使用。使用者對CPU的使用是指應用用來執行應用程式碼執行所需要的時間。相比之下,核心和系統對CPU的使用是指應用用來執行作業系統核心程式碼鎖花費的時間。高的核心或者系統CPU使用率可以表明共享資源緊迫,或者是有大量的I/O裝置互動。理想的狀態為了提高應用的效能和伸縮性,讓核心或系統CPU時間為0%,因為花在執行核心或系統程式碼的時間是可以用來執行應用程式碼的。因此CPU使用優化的一個正確方向就是儘可能減少CPU花在執行核心程式碼或者系統程式碼上的時間。

對於計算密集型應用,效能監控比監測使用者CPU使用和核心或系統CPU使用要更深層次,在計算密集型應用中,我們需要監測CPU時鐘週期內的執行執行條數(Instructions per clock;IPC)或者是每條CPU執行所使用的CPU週期(cycles per instruction;CPI)。對於計算密集型應用來說我們從這兩個維度來監測CPU是不錯的選擇,因為現代作業系統的打包CPU效能報告工具通常只會列印CPU的利用率,而不會列印CPU週期內CPU用來執行指令的時間。這意味著當CPU正在等待記憶體中的資料的時候,作業系統CPU效能報告工具也會認為CPU是正在使用的狀態,我們把這個場景叫做“Stall”,“Stall”場景經常會發生,比如在CPU正在執行指令的任何時候,只要是指令需要的資料沒有準備好,也就是沒有在暫存器或者CPU快取內,都會發生“Stall”場景。

當“Stall”場景發生的時候CPU會浪費時鐘週期,因為CPU必須要等待指令需要的資料到達暫存器或者緩衝器。而且在這個場景中,數百個CPU時鐘週期被浪費是很正常的事情,因此在計算密集型應用中,提高效能的策略是減少“Stall”場景的發生或者是增強CPU的快取使用從而使得更少的CPU週期因為等待資料而浪費掉。這類的效能監控知識已經超越了本書的內容,需要效能專家的幫助了。然而,後面講到的Oracle Solaris Studio Performance Analyzer這種效能剖析工具將會包括此類資料。

2.CPU排程佇列

除了對CPU使用的監控,我們也可以通過監控CPU執行佇列來檢查系統是否已經滿負載。執行佇列是用來儲存輕量級程式,這些程式通常是已經準備好執行了但是正在等待CPU排程而在排程佇列等待的一種狀態,當輕量級程式別當前處理器能來得及處理的數量更多的時候,排程佇列將會產生。比較深的CPU排程佇列表明系統已經滿負荷了。系統的執行佇列深度等於虛擬處理器執行不了的等待數,虛擬處理器數等於系統的硬體執行緒數。我們可以用java的api來拿到虛擬處理器數,Runtime.avaliableProcessors()。當執行佇列深度大於虛擬處理器個數的四倍或更多的時候,作業系統將會出現反應遲鈍的現象。

對於CPU排程佇列的檢測的一個通用指導是當我們發現佇列深度高於虛擬程式數一倍的時候就要注意了,但是沒有必要立即採取行動。當大於三倍或四倍或者更高的時候就要注意了,解決問題刻不容緩。

通常有兩個可選的途徑來觀察佇列的深度,第一個是通過增加CPU來分擔負載或者減少對現有CPU的負載。這種途徑從本質上減少了每個執行單元的負載執行緒數,從而減少執行執行佇列的深度。

另外的一種途徑是通過剖析系統執行的應用來增加CPU的使用率,換個說法就是尋找一種可以減少花費在垃圾回收上的CPU週期,或者尋找更好的演算法來以更少的CPU週期來執行CPU指令。效能專家通常專注後面的一種途徑:減少程式碼的執行路徑長度和更好的CPU指令選擇。JAVA程式設計師可以通過更好的執行演算法和資料結構來提高程式碼的執行效率。

3.記憶體利用率

除了CPU的使用率,系統的記憶體屬性也需要被監控,這些屬性包括比如:分頁、交換、鎖、多執行緒引起的上下文交換等。

交換通常發生在當應用需要的記憶體大於實際的實體記憶體的時候,處理這種情況作業系統通常會配置一個相應的區域叫做交換區。交換區通常位於物理磁碟上,當實體記憶體內應用耗盡的時候,作業系統會將一部分記憶體資料暫時交換到磁碟空間上,這部分記憶體區域通常是訪問頻率最低的一塊區域,而不會影響比較“忙”的記憶體區域;當被交換到磁碟區域的記憶體又被應用訪問的時候,這個時候就需要從磁碟交換區將以頁為單位讀入記憶體,交換會影響應用的效能。

虛擬機器的垃圾收集器在交換的時候效能非常差,因為垃圾收集器所訪問的大部分割槽域都是不可達的,也就是垃圾收集器會引起交換活動的發生。場景是戲劇性的,如果垃圾收集的堆區域已經被交換到了磁碟空間,這個時候將會以頁為單位發生交換,這樣才能夠被垃圾收集器所掃描到,在交換的過程中會戲劇性的引發垃圾收集器的收集時間延長,這個時候如果垃圾收集器是“Stop The World”(使得應用響應停止)的,那麼這個時間就會被延長。

4.網路I/O

分散式JAVA應用的效能和伸縮性會受到網路頻寬和網路效能的限制。例如,如果我們往網路介面傳送比他能夠處理的更多的資料包,資料包將會堆積在作業系統的緩衝區內,這將會引發應用延遲,另外其他的情況也會導致網路應用的延遲。

區分和監控的工具通常在作業系統的打包工具中很難找到。儘管linux提供了netstat命令,linux和solaris都提供了網路使用情況的實現,他們都提供了包括每秒發包、接包、錯包、衝突等資訊的統計。在乙太網中,一小部分包衝突是很正常的現象。如果錯包情況比較多那可能是網路卡有問題了。同時,儘管netstat可以統計網路介面的傳送和接收資料情況,這很難斷定網路卡是否被充分利用。例如,如果netstat -i顯示現在每秒有2500個包從網路卡發出,但是我們仍然無法判斷當前的網路利用率是100%還是1%,我們僅僅能夠知道目前有流量。這僅僅是在不知道網路包大小的情況下能夠得到的結論。簡單的說我們無法通過linux和solaris提供的netstat來判斷當前網路是否影響了效能。我們需要一些其他的工具在我們的JAVA應用執行的過程中來監測網路。

5.磁碟I/O

如果應用有對磁碟進行操作,我們需要對磁碟進行監控,來監測可能出現的磁碟效能問題。一些應用是I/O密集型的,比如資料庫。磁碟的使用通常還存在於應用日誌系統,日誌通常是我們用來記錄系統執行過程中重要資訊的。

本文轉自:碼農網 地址連結:http://www.codeceo.com/article/5-tips-java-high-performance.html

Alt text

相關文章