1、 盡量指定類的final修飾符 帶有final修飾符的類是不可派生的。
如果指定一個類為final,則該類所有的方法都是final。Java編譯器會尋找機會内聯(inline)所有的 final方法(這和具體的編譯器實作有關)。此舉能夠使性能平均提高50% 。
2、 盡量重用對象。
特别是String 對象的使用中,出現字元串連接配接情況時應用StringBuffer 代替。由于系統不僅要花時間生成對象,以後可能還需花時間對這些對象進行垃圾回收和處理。是以,生成過多的對象将會給程式的性能帶來很大的影響。
3、 盡量使用局部變量,調用方法時傳遞的參數以及在調用中建立的臨時變量都儲存在棧(Stack)中,速度較快。
其他變量,如靜态變量、執行個體變量等,都在堆(Heap)中建立,速度較慢。另外,依賴于具體的編譯器/JVM,局部變量還可能得到進一步優化。請參見《盡 可能使用堆棧變量》。
4、 不要重複初始化變量
預設情況下,調用類的構造函數時, Java會把變量初始化成确定的值:所有的對象被設定成null,整數變量(byte、short、int、long)設定成0,float和 double變量設定成0.0,邏輯值設定成false。當一個類從另一個類派生時,這一點尤其應該注意,因為用new關鍵詞建立一個對象時,構造函數鍊 中的所有構造函數都會被自動調用。
5、 在JAVA + ORACLE 的應用系統開發中,java中内嵌的SQL語句盡量使用大寫的形式,以減輕ORACLE解析器的解析負擔。
6、 Java 程式設計過程中,進行資料庫連接配接、I/O流操作時務必小心,在使用完畢後,即使關閉以釋放資源。
因為對這些大對象的操作會造成系統大的開銷,稍有不慎,會導緻嚴重的後果。
7、 由于JVM的有其自身的GC機制,不需要程式開發者的過多考慮,從一定程度上減輕了開發者負擔,但同時也遺漏了隐患,過分的建立對象會消耗系統的大量内 存,嚴重時會導緻記憶體洩露,是以,保證過期對象的及時回收具有重要意義。
JVM回收垃圾的條件是:對象不在被引用;然而,JVM的GC并非十分的機智,即使對象滿足了垃圾回收的條件也不一定會被立即回收。是以,建議我們在對象 使用完畢,應手動置成null。
8、 在使用同步機制時,應盡量使用方法同步代替代碼塊同步。
9、 盡量減少對變量的重複計算
10、盡量采用lazy loading 的政策,即在需要的時候才開始建立。
11、慎用異常
異常對性能不利。抛出異常首先要建立一個新的對象。Throwable接口的構造函數調用名為fillInStackTrace()的本地 (Native)方法,fillInStackTrace()方法檢查堆棧,收集調用跟蹤資訊。隻要有異常被抛出,VM就必須調整調用堆棧,因為在處理過 程中建立了一個新的對象。 異常隻能用于錯誤處理,不應該用來控制程式流程。
12、不要在循環中使用:
Try {
} catch() {
}
應把其放置在最外層。
13、StringBuffer 的使用:
StringBuffer表示了可變的、可寫的字元串。
有三個構造方法 :
StringBuffer (); //預設配置設定16個字元的空間
StringBuffer (int size); //配置設定size個字元的空間
StringBuffer (String str); //配置設定16個字元+str.length()個字元空間
你可以通過StringBuffer的構造函數來設定它的初始化容量,這樣可以明顯地提升性能。
這裡提到的構造函數是StringBuffer(int length),length參數表示目前的StringBuffer能保持的字元數量。你也可以使用ensureCapacity(int minimumcapacity)方法在StringBuffer對象建立之後設定它的容量。首先我們看看StringBuffer的預設行為,然後再找 出一條更好的提升性能的途徑。
StringBuffer在内部維護一個字元數組,當你使用預設的構造函數來建立StringBuffer對象的時候,因為沒有設定初始化字元長 度,StringBuffer的容量被初始化為16個字元,也就是說預設容量就是16個字元。當StringBuffer達到最大容量的時候,它會将自身 容量增加到目前的2倍再加2,也就是(2*舊值+2)。如果你使用預設值,初始化之後接着往裡面追加字元,在你追加到第16個字元的時候它會将容量增加到 34(2*16+2),當追加到34個字元的時候就會将容量增加到70(2*34+2)。無論何事隻要StringBuffer到達它的最大容量它就不得 不建立一個新的字元數組然後重新将舊字元和新字元都拷貝一遍――這也太昂貴了點。是以總是給StringBuffer設定一個合理的初始化容量值是錯不了 的,這樣會帶來立竿見影的性能增益。StringBuffer初始化過程的調整的作用由此可見一斑。是以,使用一個合适的容量值來初始化 StringBuffer永遠都是一個最佳的建議。
14、合理的使用Java類 java.util.Vector。
簡單地說,一個Vector就是一個java.lang.Object執行個體的數組。Vector與數組相似,它的元素可以通過整數形式的索引通路。但 是,Vector類型的對象在建立之後,對象的大小能夠根據元素的增加或者删除而擴充、縮小。請考慮下面這個向Vector加入元素的例子:
Object bj = new Object();
Vector v = new Vector(100000);
for(int I=0;
I<100000; I++) { v.add(0,obj); }
除非有絕對充足的理由要求每次都把新元素插入到Vector的前面,否則上面的代碼對性能不利。在預設構造函數中,Vector的初始存儲能力 是10個元素,如果新元素加入時存儲能力不足,則以後存儲能力每次加倍。Vector類就對象StringBuffer類一樣,每次擴充存儲能力時,所有 現有的元素都要複制到新的存儲空間之中。下面的代碼片段要比前面的例子快幾個數量級:
for(int I=0; I<100000; I++) { v.add(obj); }
同樣的規則也适用于Vector類的remove()方法。由于Vector中各個元素之間不能含有“空隙”,删除除最後一個元素之外的任意其 他元素都導緻被删除元素之後的元素向前移動。也就是說,從Vector删除最後一個元素要比删除第一個元素“開銷”低好幾倍。
假設要從前面的Vector删除所有元素,我們可以使用這種代碼:
for(int I=0; I<100000; I++)
{
v.remove(0);
但是,與下面的代碼相比,前面的代碼要慢幾個數量級:
v.remove(v.size()-1);
從Vector類型的對象v删除所有元素的最好方法是:
v.removeAllElements();
假設Vector類型的對象v包含字元串“Hello”。考慮下面的代碼,它要從這個Vector中删除“Hello”字元串:
String s = “Hello”;
int i = v.indexOf(s);
if(I != -1) v.remove(s);
這些代碼看起來沒什麼錯誤,但它同樣對性能不利。在這段代碼中,indexOf()方法對v進行順序搜尋尋找字元串 “Hello”,remove(s)方法也要進行同樣的順序搜尋。改進之後的版本是:
if(I != -1) v.remove(i);
這個版本中我們直接在remove()方法中給出待删除元素的精确索引位置,進而避免了第二次搜尋。一個更好的版本是:
String s = “Hello”; v.remove(s);
最後,我們再來看一個有關Vector類的代碼片段:
for(int I=0; I++;I < v.length)
如果v包含100,000個元素,這個代碼片段将調用v.size()方法100,000次。雖然size方法是一個簡單的方法,但它仍舊需要 一次方法調用的開銷,至少JVM需要為它配置以及清除堆棧環境。在這裡,for循環内部的代碼不會以任何方式修改Vector類型對象v的大小,是以上面 的代碼最好改寫成下面這種形式:
int size = v.size(); for(int I=0; I++;I<size)
雖然這是一個簡單的改動,但它仍舊赢得了性能。畢竟,每一個CPU周期都是寶貴的。
15、當複制大量資料時,使用System.arraycopy()指令。
16、代碼重構:增強代碼的可讀性。
17、不用new關鍵詞建立類的執行個體
用new關鍵詞建立類的執行個體時,構造函數鍊中的所有構造函數都會被自動調用。但如果一個對象實作了Cloneable接口,我們可以調用它的 clone()方法。clone()方法不會調用任何類構造函數。
在使用設計模式(Design Pattern)的場合,如果用Factory模式建立對象,則改用clone()方法建立新的對象執行個體非常簡單。例如,下面是Factory模式的一個 典型實作:
public static Credit getNewCredit() {
return new Credit();
改進後的代碼使用clone()方法,如下所示:
private static Credit BaseCredit = new Credit();
return (Credit) BaseCredit.clone();
上面的思路對于數組處理同樣很有用。
18、乘法和除法,用移位操作替代乘法操作可以極大地提高性能。
19、在JSP頁面中關閉無用的會話。
一個常見的誤解是以為session在有用戶端通路時就被建立,然而事實是直到某server端程式調用 HttpServletRequest.getSession(true)這樣的語句時才被建立,注意如果JSP沒有顯示的使用 <%@pagesession=”false”%> 關閉session,則JSP檔案在編譯成Servlet時将會自動加上這樣一條語句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隐含的session對象的來曆。由于session會消耗内 存資源,是以,如果不打算使用session,應該在所有的JSP中關閉它。
對于那些無需跟蹤會話狀态的頁面,關閉自動建立的會話可以節省一些資源。使用如下page指令:<%@ page session=”false”%>
20、JDBC與I/O
如果應用程式需要通路一個規模很大的資料集,則應當考慮使用塊提取方式。預設情況下,JDBC每次提取32行資料。舉例來說,假設我們要周遊一個5000 行的記錄集,JDBC必須調用資料庫157次才能提取到全部資料。如果把塊大小改成512,則調用資料庫的次數将減少到10次。
21、Servlet與記憶體使用
許多開發者随意地把大量資訊儲存到使用者會話之中。一些時候,儲存在會話中的對象沒有及時地被垃圾回收機制回收。從性能上看,典型的症狀是使用者感到系統周期 性地變慢,卻又不能把原因歸于任何一個具體的元件。如果監視JVM的堆空間,它的表現是記憶體占用不正常地大起大落。
解決這類記憶體問題主要有二種辦法。第一種辦法是,在所有作用範圍為會話的Bean中實作HttpSessionBindingListener接口。這 樣,隻要實作valueUnbound()方法,就可以顯式地釋放Bean使用的資源。
另外一種辦法就是盡快地把會話廢棄。大多數應用伺服器都有設定會話廢棄間隔時間的選項。另外,也可以用程式設計的方式調用會話的 setMaxInactiveInterval()方法,該方法用來設定在廢棄會話之前,Servlet容器允許的客戶請求的最大間隔時間,以秒計。
22、使用緩沖标記
一些應用伺服器加入了面向JSP的緩沖标記功能。例如,BEA的WebLogic Server從6.0版本開始支援這個功能,Open Symphony工程也同樣支援這個功能。JSP緩沖标記既能夠緩沖頁面片斷,也能夠緩沖整個頁面。當JSP頁面執行時,如果目标片斷已經在緩沖之中,則 生成該片斷的代碼就不用再執行。頁面級緩沖捕獲對指定URL的請求,并緩沖整個結果頁面。對于購物籃、目錄以及門戶網站的首頁來說,這個功能極其有用。對 于這類應用,頁面級緩沖能夠儲存頁面執行的結果,供後繼請求使用。
23、選擇合适的引用機制
在典型的JSP應用系統中,頁頭、頁腳部分往往被抽取出來,然後根據需要引入頁頭、頁腳。目前,在JSP頁面中引入外部資源的方法主要有兩 種:include指令,以及include動作。
include指令:例如<%@ include file=”copyright.html” %>。該指令在編譯時引入指定的資源。在編譯之前,帶有include指令的頁面和指定的資源被合并成一個檔案。被引用的外部資源在編譯時就确定, 比運作時才确定資源更高效。
include動作:例如<jsp:include page=”copyright.jsp” />。該動作引入指定頁面執行後生成的結果。由于它在運作時完成,是以對輸出結果的控制更加靈活。但時,隻有當被引用的内容頻繁地改變時,或者在對 首頁面的請求沒有出現之前,被引用的頁面無法确定時,使用include動作才合算。
24、及時清除不再需要的會話
為了清除不再活動的會話,許多應用伺服器都有預設的會話逾時時間,一般為30分鐘。當應用伺服器需要儲存更多會話時,如果記憶體容量不足,作業系統會把部分 記憶體資料轉移到磁盤,應用伺服器也可能根據“最近最頻繁使用”(Most Recently Used)算法把部分不活躍的會話轉儲到磁盤,甚至可能抛出“記憶體不足”異常。在大規模系統中,串行化會話的代價是很昂貴的。當會話不再需要時,應當及時 調用HttpSession.invalidate()方法清除會話。HttpSession.invalidate()方法通常可以在應用的退出頁面調 用。
25、不要将數組聲明為:public static final 。
26、HashMap的周遊效率讨論
經常遇到對HashMap中的key和value值對的周遊操作,有如下兩種方法:Map<String, String[]> paraMap = new
HashMap<String, String[]>();
…………….//第一個循環
Set<String> appFieldDefIds = paraMap.keySet();
for (String appFieldDefId : appFieldDefIds) {
String[] values = paraMap.get(appFieldDefId);
……
//第二個循環
for(Entry<String, String[]> entry : paraMap.entrySet()){
String appFieldDefId = entry.getKey();
String[] values = entry.getValue();
…….
第一種實作明顯的效率不如第二種實作。
分析如下 Set<String> appFieldDefIds = paraMap.keySet(); 是先從HashMap中取得keySet
代碼如下:
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
private class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
public int size() {
return size;
public boolean contains(Object o) {
return containsKey(o);
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
public void clear() {
HashMap.this.clear();
其實就是傳回一個私有類KeySet, 它是從AbstractSet繼承而來,實作了Set接口。
再來看看for/in循環的文法
for(declaration : expression)
statement
在執行階段被翻譯成如下各式
for(Iterator<E> #i = (expression).iterator(); #i.hashNext();){
declaration = #i.next();
是以在第一個for語句for (String appFieldDefId : appFieldDefIds) 中調用了HashMap.keySet().iterator()
而這個方法調用了newKeyIterator()
Iterator<K> newKeyIterator() {
return new KeyIterator();
private class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
是以在for中還是調用了
在第二個循環for(Entry<String, String[]> entry : paraMap.entrySet())中使用的Iterator是如下的一個内部類
private class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
return nextEntry();
此時第一個循環得到key,第二個循環得到HashMap的Entry效率就是從循環裡面展現出來的第二個循環此緻可以直接取key和value值 而第一個循環還是得再利用HashMap的get(Object key)來取value值現在看看HashMap的get(Object key)方法
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length); //Entry[] table
Entry<K,V> e = table;
while (true) {
if (e == null)
return null;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
其實就是再次利用Hash值取出相應的Entry做比較得到結果,是以使用第一中循環相當于兩次進入HashMap的Entry