Java程序爲什麼需要調優(《大話Java性能優化》第一章第一節)

2011年1月,新加坡飛往杭州的航班。飛行時間很長,大約6個小時,坐在四周的人很快熟悉了,互相攀談起來。有一位小姑娘,16、7歲的摸樣,長得很漂亮,默默地坐在座位上,當有熱心的阿姨問起她的情況,她帶着疲倦自我介紹起來,“我在新加坡念初三,那所學校一點都不好,我在成都是最好的初中畢業的,也考上了成都最好的高中,但是,我的父母,他們一定要我來新加坡復讀初三,讓我考新加坡的高中,我一點都不喜歡這裏,這裏的同學看不起我們這些大陸學生,經常上課找大陸來的老師麻煩,經常辱罵我們,我煩透了!!!”。對,這不是自我介紹,這是人接近奔潰邊緣的歇斯底里。也就是在當時,我做出了決定,我絕不會讓我的女兒這樣遠離我,一個人在很年幼的時候就必須獨立面對生活的困難,絕不。不論她的父母出於什麼原因讓她去國外唸書,我所看到的,是讓一個不適合承受壓力的人承擔了巨大的壓力,這就像本書我想要和大家討論的話題—“性能優化”,我們不能隨意地指出性能優化的方案,就像隨意指派由那位小姑娘來完成全家的決定一樣。我們必須經過嚴密的研究、測試及驗證,明確真正的性能瓶頸原因後才能開始着手。

隨着互聯網業務的不斷拓展、繁榮,越來越多的系統架構開始參照互聯網+企業的系統架構方式。筆者認爲,任何技術都離不開對業務需求的支撐,所以開始研究程序性能問題之前,我們需要先了解系統業務邏輯。

12306[1]這個網站一直被全國人民所詬病,它確實存在一些問題,但是這些看似簡單的問題,其背後牽扯着複雜的系統架構設計,這些設計最終是爲業務需求服務的,即12306的職責是爲旅客的需求服務的,而我們設計的程序又是爲12306服務的,所有的意識歸到最終就是服務意識。我們來看一下12306的業務,12306需要支持海量併發查詢,查時間、查車次、查座位、查鋪位,同時,對應的下單過程也伴隨着海量併發的數據庫操作。據說,淘寶在雙十一期間也只有幾百萬用戶,而春運期間搶購火車票是全國人民的統一活動,瞬時訪問數量有千萬級別甚至是億級別的,據說12306的高峯訪問是10億PV[2],這些訪問主要集中在早8點到10點,每秒PV在高峯時上千萬。

再來看看其他的業務系統,奧運會期間的奧運票務系統採用的是抽獎的方式,不存在先來先得搶購需求,由於是事後抽獎,事前只負責收集信息,所以不需要保證數據一致性,這也就沒有高強度併發鎖的需求,很容易通過水平擴展方式克服性能瓶頸。B2C[3]網站一般實時性要求不高,比如下單,用戶提交下單後,訂單並不是馬上被處理的,而是等待一定時間後,用戶纔會收到訂單是否確認的通知,這樣就確保了數據不需要立即被處理,沒有了數據高併發同步的需求。也就是說,在高併發要求下的數據一致性是通常情況下的性能瓶頸點,也是技術難點之一。

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調優等之外,堅持引入服務器、網絡、雲計算、虛擬化等多維技術點的原因。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章