有很多介紹基本的Java應(yīng)用性能調(diào)整的文章。他們都討論些簡單的技術(shù),諸如使用StringBuffer而不用String,使用synchronized關(guān)鍵字的開銷等等。
這篇文章不再介紹這些東西。相反,我們關(guān)注能幫助你的基于Web的應(yīng)用更快、可升級型更好的技巧。一些技巧很詳細(xì),其他的相對簡短,但所有的都很有用。最后以一些你可提供給你的管理者的建議結(jié)束。
我寫這篇文章的靈感來自于當(dāng)我的同事和我一起回憶我們的.com(dot-com)時代的時候――我們?nèi)绾卧O(shè)計能支持成千上萬的用戶和擁有緊密代碼的系統(tǒng),我們?nèi)绾螌τ星致孕缘闹旅驌簟S袝r在為復(fù)用設(shè)計和為性能設(shè)計之間有一個權(quán)衡。基于我的情況,性能每次都獲勝。即使你的商務(wù)顧客無需理解代碼復(fù)用,但是他們知道快速(fast-performing)的系統(tǒng)是怎么回事。讓我們開始看看我們的技巧。
如何使用Exception Exception降低性能。一個異常拋出首先需要創(chuàng)建一個新的對象。Throwable接口中的構(gòu)造器調(diào)用名為fillInStackTrace()的本地方法。這個方法負(fù)責(zé)巡檢棧的整個框架來收集跟蹤信息。這樣無論何時有異常拋出,它要求虛擬機裝載調(diào)用棧,因為一個新的對象在中部被創(chuàng)建。
異常應(yīng)當(dāng)僅用于有錯誤發(fā)生時,而不要控制流。
我有機會在一個專門用于無線內(nèi)容市場的網(wǎng)站(名字故意隱去了)看到一段代碼,其中開發(fā)者完全可以使用一個簡單的對照來查看對象是否為空。相反,他或她跳過了這個檢查而實際上拋出Null-PointerException。
不要兩次初始化變量 Java通過調(diào)用獨特的類構(gòu)造器默認(rèn)地初始化變量為一個已知的值。所有的對象被設(shè)置成null,integers (byte, short, int, long)被設(shè)置成0,float和double設(shè)置成0.0,Boolean變量設(shè)置成false。這對那些擴展自其它類的類尤其重要,這跟使用一個新的關(guān)鍵詞創(chuàng)建一個對象時所有一連串的構(gòu)造器被自動調(diào)用一樣。
對新的關(guān)鍵詞使用優(yōu)選法則 正如前面提到的,通過使用一個新的關(guān)鍵詞創(chuàng)建一個類的實例,在這個鏈中的所有構(gòu)造器將被調(diào)用。如果你需要創(chuàng)建一個類的新實例,你可以使用一個實現(xiàn)了cloneable接口的對象的clone()方法。該clone方法不調(diào)用任何類的構(gòu)造器。
如果你已經(jīng)使用了設(shè)計模式作為你的體系結(jié)構(gòu)的一部分,并且使用了工廠模式創(chuàng)建對象,變化會很簡單。下面所列是工廠模式的典型實現(xiàn)。
public static Account getNewAccount() {
return new Account();
}
使用了clone方法的refactored代碼看起來可能像下面這樣:
private static Account BaseAccount = new Account();
public static Account getNewAccount() {
return (Account) BaseAccount.clone();
}
以上的思路對實現(xiàn)數(shù)組同樣有用。
如果你在應(yīng)用中沒有使用設(shè)計模式,我建議你停止讀這篇文章,趕快跑到(不要走)書店挑一本四人著的《設(shè)計模式》。
在任何可能的地方讓類為Final 標(biāo)記為final的類不能被擴展。在《核心Java API》中有大量這個技術(shù)的例子,諸如java.lang.String。將String類標(biāo)記為final阻止了開發(fā)者創(chuàng)建他們自己實現(xiàn)的長度方法。
更深入點說,如果類是final的,所有類的方法也是final的。Java編譯器可能會內(nèi)聯(lián)所有的方法(這依賴于編譯器的實現(xiàn))。在我的測試?yán)铮乙呀?jīng)看到性能平均增加了50%。
在任何可能的地方使用局部變量 屬于方法調(diào)用部分的自變量和聲明為此調(diào)用一部分的臨時變量存儲在棧中,這比較快。諸如static,實例(instance)變量和新的對象創(chuàng)建在堆中,這比較慢。局部變量的更深入優(yōu)化依賴于你正在使用的編譯器或虛擬機。
使用Nonblocking I/O 當(dāng)前的JDK版本不支持nonblocking I/O API,很多應(yīng)用試圖通過創(chuàng)建大量的線程(目光長遠(yuǎn)得用在池中)來避免阻塞。正如前述,在Java中創(chuàng)建線程有嚴(yán)重的開銷。
典型的你可能看到應(yīng)用中實現(xiàn)的線程需要支持并發(fā)I/O流,像Web 服務(wù)器,并quote and auction components.
JDK1.4介紹了一個nonblocking I/O包(java.nio)。如果你必須保留在較早版本的JDK,有添加了支持nonblocking I/O的第三方包。
:www.cs.berkeley.edu/~mdw/proj/java-nbio/download.html.
停止小聰明 很多開發(fā)人員在腦子中編寫可復(fù)用和靈活的代碼,而有時候在他們的程序中就產(chǎn)生額外的開銷。曾經(jīng)或者另外的時候他們編寫了類似這樣的代碼:
public void doSomething(File file) {
FileInputStream fileIn = new FileInputStream(file);
// do something
他夠靈活,但是同時他們也產(chǎn)生了更多的開銷。這個主意背后做的事情是操縱一個InputStream,而不是一個文件,因此它應(yīng)該重寫如下:
public void doSomething(InputStream inputStream){
// do something
乘法和除法 我有太多的東東適用于摩爾法則――它聲明CPU功率每年成倍增長。"摩爾法則"表明每年由開發(fā)者所寫的差勁的代碼數(shù)量三倍增加,劃去了摩爾法則的任何好處。
考慮下面的代碼:
for (val = 0; val < 100000; val +=5) { shiftX = val * 8; myRaise = val * 2; }
如果我們狡猾的利用位移(bit),性能將會六倍增加。這是重寫的代碼:
for (val = 0; val < 100000; val += 5) { shiftX = val << 3; myRaise = val << 1; }
代替了乘以8,我們使用同等效果的左移3位。每一個移動相當(dāng)于乘以2,變量myRaise對此做了證明。同樣向右移位相當(dāng)于除以2,當(dāng)然這會使執(zhí)行速度加快,但可能會使你的東東以后難于理解;所以這只是個建議。
選擇一個基于垃圾收集實現(xiàn)的虛擬機 許多人可能會對Java規(guī)范不需要實現(xiàn)垃圾收集感到驚訝。設(shè)想時代已經(jīng)是我們都擁有無限內(nèi)存計算機。總之,垃圾收集器日常事務(wù)就是負(fù)責(zé)發(fā)現(xiàn)和拋出(hence garbage)不再需要的對象。垃圾收集必須發(fā)現(xiàn)那些對象不再被程序指向,并且使被對象占用的棧內(nèi)存被釋放掉。它還負(fù)責(zé)運行任何被釋放對象的finalizer。
垃圾收集故意不允許你釋放并非由你分配的內(nèi)存,從而幫助你確保程序完整,當(dāng)JVM確定CPU時間的時間表并且當(dāng)垃圾收集器運行時,這個進程也產(chǎn)生開銷。
垃圾收集器有兩個不同的步驟執(zhí)行他們的工作。
實現(xiàn)了定位計算的垃圾收集器在棧中為每一個對象保留一個計數(shù)。當(dāng)一個對象被創(chuàng)建并且對它的一個定位被分配給一個變量,計數(shù)增加。當(dāng)對象越出范圍,定位計數(shù)被設(shè)置成0并且對象可以被垃圾收集。這個步驟允許參考計數(shù)器運行在與程序執(zhí)行有關(guān)的短時間增量內(nèi)。定位計數(shù)在父子彼此擁有定位的應(yīng)用里運行不正常。每次一個對象刷新時也會有定位計數(shù)增加和減少的開銷。
實現(xiàn)了跟蹤的垃圾收集器從根節(jié)點開始跟蹤一列定位。對象發(fā)現(xiàn)跟蹤是否被標(biāo)記。在這個過程完成后,知道不可達(dá)的任何沒標(biāo)記的對象可以被垃圾收集。這可能以位圖(bitmap)形式實現(xiàn)或者在對象中被設(shè)置標(biāo)志。此技術(shù)參考"Mark and Sweep."(reference:定位,翻譯成“指向”好像更容易理解,是Java語言對在用對象的一個跟蹤指針。譯者著)
給你的管理人員提建議 其他方法可被用來使你的基于Web的應(yīng)用更快并且更可升級。可實現(xiàn)的最簡單的技術(shù)通常是支持cluster的策略。使用cluster,一組服務(wù)器能夠一起透明的提供服務(wù)。多數(shù)應(yīng)用服務(wù)器允許你獲得cluster支持而不需要改變你的應(yīng)用――一個大的勝利。
當(dāng)然在執(zhí)行此步驟之前你可能需要考慮來自你使用的應(yīng)用服務(wù)器提供商附加的許可權(quán)利。
當(dāng)看到cluster策略會有許多額外的事情考慮。經(jīng)常在體系結(jié)構(gòu)中產(chǎn)生的一個缺點是擁有有狀態(tài)會話。如果cluster中的一個服務(wù)器或者進程當(dāng)?shù)簦琧luster會舍棄整個應(yīng)用。為防止此類事情發(fā)生,cluster必須給cluster中的所有成員不斷復(fù)制會話Bean的狀態(tài)。確保你也限制了存儲在會話中的對象的大小和數(shù)量,因為這些也需要被復(fù)制。
Cluster也允許你分期度量你的Web站點的部分。如果你需要度量靜態(tài)部分,你可以添加Web服務(wù)器。如果你需要度量動態(tài)生成的部分,你可以添加應(yīng)用服務(wù)器。
在你已經(jīng)把你的系統(tǒng)放入cluster后,下一個讓你的應(yīng)用跑得更快的建議步驟是選擇一個更好的虛擬機。看看Hotspot虛擬機或者其他的飛速發(fā)展中的執(zhí)行優(yōu)化的虛擬機。隨同虛擬機,看看更好的編譯器是一個更好的主意。
如果你使用了幾個這兒提到的行業(yè)技術(shù)插件,并且仍然不能獲得你要的可升級性和高可用性,那么我建議一個可靠的調(diào)試策略。策略的第一步是為可能的瓶頸檢查整個體系結(jié)構(gòu)。通常,這在你的作為單線程組件或者有很多輔助連接線組件的UML流圖中很容易識別出來。
最后的步驟是產(chǎn)生一個整個代碼的詳細(xì)性能估價。
確保你的管理人員至少為此安排了整個項目時間的20%;否則不足的時間可能不止危及你整個成功的安全,還會導(dǎo)致你向系統(tǒng)引入新的缺點。
許多組織者在適當(dāng)?shù)奈恢脹]有嚴(yán)格意義的測試基礎(chǔ)而歸咎于成本考慮也是錯誤的。確保你的QA環(huán)境真實反映你的生產(chǎn)環(huán)境,并且你的QA測試考慮以不同的負(fù)載測試應(yīng)用,包括在最大的預(yù)期并發(fā)用戶時一個基于低負(fù)載和一個完全負(fù)載的測試。
性能測試,有時測試一個系統(tǒng)的穩(wěn)定性,可能需要在每天,甚至每周的整個時期的不同關(guān)節(jié)都運行。
轉(zhuǎn)自:
http://www.1piao.net/articles/view.asp?p=2006/2/1142260628218