Java程式為什麼需要調優(《大話Java效能優化》第一章第一節)
1.1為什麼需要調優
經歷了多年的發展,Java已由一門單純的計算機程式語言,演變為一套強大的技術體系平臺。根據不同的技術規範,Java設計者們將Java劃分為3種結構獨立但卻又彼此依賴的技術體系分支,分別是Java SE、Java EE和Java ME,其中Java EE被廣泛使用在企業級領域,出了包括Java API元件外,還擴充有Web元件、事務元件、分散式元件、EJB元件、訊息元件等,並持續到現在。綜合Java EE的這些技術,開發人員可以構建出一個具備高效能、結構嚴謹的企業級應用,並且JavaEE也是用於構建SOA架構的首選平臺。
Java的持續發展要感謝Google,正是Google將Java作為Andriod作業系統的應用層程式語言,使得Java可以在PC時代、移動網際網路時代都得到迅猛發展,可以用於手持移動裝置、嵌入式裝置、個人電腦、高效能的叢集伺服器或大型機。
前面提起過,高併發情況下的資料高度實時一致性需求是很難實現的。對於一個網站來說,併發瀏覽網頁造成的高負載較容易處理,高併發的查詢負載也可以處理,但是實時下單是最難處理的,因為下單需要訪問當前的庫存量,對於12306網站來說,庫存量就是指火車票的庫存,據說蘋果CEO庫克[1]正是因為處理好了庫存問題才得以繼任喬幫主的寶座。目前來看,很多B2C網站的下單都是通過非同步方式來實現的,這樣的做法可以避免資料高度一致性要求。
淘寶模式相較於傳統B2C網站有一個優勢,即它不需要查詢庫存。B2C網站擁有自己的倉庫,每次下單前,都需要首先查詢距離客戶最近的倉庫是否有庫存,這樣的計算量累計後會很大。試想,你在上海買一本書,如果上海附近的倉庫沒貨,我們需要先計算哪個倉庫離上海最近,又有這本書。淘寶網站由於本身商業模式的原因,它不需要去實時檢查庫存,反而對於效能擴充套件較為容易。
的確我們可以通過Nginx[2]來搞定每秒10萬的靜態請求,只要有足夠的頻寬、I/O,伺服器的併發計算能力夠強,可以很容易地處理併發連線數10萬。但是如果我們引入了大量的業務邏輯,那就不是單純的訪問問題了,該解決方案也就成了浮雲。
除了業務需求、程式執行方式之外,程式本身是由基礎程式設計技術、系統架構、網路技術、作業系統、硬體伺服器等諸多元件組成的。
計算機專家在問題求解時非常重視表示式簡潔性的價值。Unix的先驅者KenThompson曾經說過非常著名的一句話:“丟棄1000行程式碼的那一天是我最有成效的一天之一。”這對於任何一個需要持續支援和維護的軟體專案來說,都是一個當之無愧的目標。早期的Lisp貢獻者Paul Graham甚至將語言的簡潔性等同為語言的能力。這種對能力的認識讓我們把可以編寫緊湊、簡介的程式碼作為許多現代軟體專案選擇語言的首要標準。
任何程式都可以通過重構,去除多餘的程式碼或無用的佔位符,如空格,變得更加簡短,不過某些語言天生就善於表達,也就特別適合於簡短程式的編寫。APL語言的設計理念是利用特殊的圖形符號讓程式設計師用很少量的程式碼就可以編寫功能強大的程式。這類程式如果實現得當,可以很好地對映成標準的數學表示式。簡潔的語言在快速建立小指令碼時非常的高效,特別是在目的不會被簡潔所掩蓋的簡潔明確的問題域中。
相比於其他程式設計語言,Java語言的冗長已經名聲在外。其主要原因是由於程式開發社群中所形成的慣例,在完成任務時,很多情況下,要更大程度地考慮描述性和控制。例如,長期來看,長變數名會讓大型程式碼庫的可讀性和可維護性更強。描述性的類名通常會對映為檔名,在向已有系統中增加新功能時,會顯得很清晰。如果能夠一直堅持下去,描述性名稱可以極大簡化用於表明應用中某一特定的功能的文字搜尋。這些實踐讓Java在大型複雜程式碼庫的大規模實現中取得了極大的成功。
相對於傳統的32位虛擬機器,64位虛擬機器所具備的最大優勢就是可以訪問大記憶體,32位虛擬機器做的最大可用記憶體空間被限定在了4GB,並且Java堆區的大小如果是在Windows平臺下最大隻能設定到1.5GB,而在Linux平臺下最大也只能設定到2GB-3GB的上限,也就是說,Java堆區的記憶體大小設定還需要依賴於具體的操作平臺。既然32位虛擬機器無法滿足大記憶體消耗的應用場景,那麼64位虛擬機器的出現則是順理成章的,64位虛擬機器之所以能夠訪問大記憶體,是因為其採用了64位的指標架構,這也是定址訪問大記憶體的關鍵要素。
在JDK1.6Update14版本之前,64位虛擬機器的綜合性能表現實際上是不如32位虛擬機器的,這主要是因為OOPS(Ordinary Object Pointers,普通物件指標)從32位膨脹到64位後,CPU Cache Line中的可用OOPS變少,這樣一來就會直接影響並降低CPU的快取使用率,這就是64位虛擬機器在效能上之所以落後於32位虛擬機器的主要原因。其次由於部署在64位虛擬機器上的效能都需要用到大記憶體,尤其是網際網路專案,經常需要使用多達幾十乃至幾百GB的記憶體,這對於傳統的32位虛擬機器將無法承載,只能依靠64位虛擬機器去支撐。但是管理這麼大的記憶體開銷對於GC來說將會是一場非常嚴峻的考驗,甚至很有可能去導致GC在執行記憶體回收期間消耗更長的時間,同時也意味著工作執行緒的等待時間將會延長。隨著如今64位虛擬機器的逐漸成熟,指標壓縮將會通過對齊補白等操作將64位指標壓縮為32位,以此改善CPU快取使用率達到提升64位虛擬機器執行效能的目的。
對於小型專案來說,簡潔性則更受青睞,某些語言非常適於短指令碼編寫或者在命令提示符下的互動式探索程式設計。Java作為通用性語言,則更適用於編寫跨平臺的工具。在這種情況下,“冗長Java”的使用並不一定能夠帶來額外的價值。雖然在變數命名等方面,程式碼風格可以改變,不過從歷史情況來看,在一些基本的層面上,與其他語言相比,完成同樣的任務,Java語言仍需更多的字元。為了應對這些限制,Java語言一直在不斷地更新,以包含一些通常稱為“語法糖”的功能。用這些習語可以實現更少的字元表示相同功能的目標。與其對應的更加冗長的配對物相比,這些習語更受程式開發社群的歡迎,通常會被社群作為通用用法快速地採用。
現代CPU架構將多核、多硬體執行執行緒技術推向前臺,這意味著我們可以利用更多的CPU資源做更多的工作。然而,要利用好這些額外的CPU資源,運行於其上的程式必須要能夠接受並行工作要求。通俗點講,這些程式需要按照多執行緒的方式構造或設計才能充分利用額外的硬體執行緒。
最近這幾年,伺服器端網路使用的基礎通訊技術並沒有取得太大的進步。伺服器端大多設在絕不允許服務中斷的關鍵任務環境中,新技術很難滲透,也很難植根於這樣的環境。但正因如此,伺服器端的多餘部分才得以被剔除,逐漸地形成了非常精簡單純的風格。網路的基礎技術可以說已經成型了,然而在網路上執行的網路裝置和伺服器的技術仍然踩著現在進行時的節奏在持續不斷地爆發性發展,由此出現了虛擬技術和網路儲存技術等基於網路的創新技術。如今,它們已經在系統中不可或缺。隨著這些技術的發展,人們追求的網路形態和網路設計的方式也在時刻發生著變化,基礎架構工程師和伺服器工程師必須能靈活應對這些變化才行,對應地,軟體設計程式設計師也需要有針對性地做出應對措施。
虛擬化技術是一種資源管理技術,是將計算機的各種實體資源,如伺服器、網路、記憶體及儲存等,予以抽象、轉換後呈現出來。此舉打破了實體結構間的不可切割的障礙,使使用者可以用比原本的形態更好的方式來應用這些資源。一般所指的虛擬化資源包括計算能力和資料儲存介質,這些資源的虛擬部分不受現有資源的架設方式、地域或物理形態所限制。虛擬化技術在帶來成本節省和運維便利的同時,也帶來了一些新的挑戰,對架構師、運維人員、程式設計師等角色提出了新的要求。在解決了物理設施的虛擬化問題之後,我們的目光可能就會轉移到應用程式本身上來,因為這才是真正為使用者體現支付價值的關鍵所在。部署在虛擬化環境上的Java應用與物理環境上的應用存在明顯的區別。
綜上所述,效能優化本身對於程式效能是至關重要的,同時效能優化也是一門綜合性課程,雖然本書針對的是Java程式的效能優化,但是依然需要考慮綜合性因素。作者認為,隨著IT技術的蓬勃、快速發展,效能調優已經不單純是程式碼級別的調優,它是一個對綜合性知識的深入理解需求,我們只有結合多方面的技術才能真正找到合理的解決方案。這也是本書除了深入介紹Java程式調優、JVM調優等之外,堅持引入伺服器、網路、雲端計算、虛擬化等多維技術點的原因。