1. 程式人生 > >java效能調優(轉載)

java效能調優(轉載)

1.用new關鍵詞建立類的例項時,建構函式鏈中的所有建構函式都會被自動呼叫。但如果一個物件實現了Cloneable介面,我們可以呼叫它的clone()方法。clone()方法不會呼叫任何類建構函式。 

在使用設計模式(Design Pattern)的場合,如果用Factory模式建立物件,則改用clone()方法建立新的物件例項非常簡單。例如,下面是Factory模式的一個典型實現: 
public static Credit getNewCredit() {return new Credit();}  改進後的程式碼使用clone()方法,如下所示:private static Credit BaseCredit = new Credit();public static Credit getNewCredit() {return (Credit) BaseCredit.clone();} 



面的思路對於陣列處理同樣很有用。 
2. 使用非阻塞I/O 

版本較低的JDK不支援非阻塞I/O API。為避免I/O阻塞,一些應用採用了建立大量執行緒的辦法(在較好的情況下,會使用一個緩衝池)。這種技術可以在許多必須支援併發I/O流的應用中見到,如Web伺服器、報價和拍賣應用等。然而,建立Java執行緒需要相當可觀的開銷。 

3. 慎用異常 

異常對效能不利。丟擲異常首先要建立一個新的物件。Throwable介面的建構函式呼叫名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆疊,收集呼叫跟蹤資訊。只要有異常被丟擲,VM就必須調整呼叫堆疊,因為在處理過程中建立了一個新的物件。異常只能用於錯誤處理,不應該用來控制程式流程。 


4. 不要重複初始化變數 

預設情況下,呼叫類的建構函式時, Java會把變數初始化成確定的值:所有的物件被設定成null,整數變數(byte、short、int、long)設定成0,float和double變數設定成0.0,邏輯值設定成false。當一個類從另一個類派生時,這一點尤其應該注意,因為用new關鍵詞建立一個物件時,建構函式鏈中的所有建構函式都會被自動呼叫。

5. 儘量指定類的final修飾符 

帶有final修飾符的類是不可派生的。在Java核心API中,有許多應用final的例子,例如java.lang.String。為String類指定final防止了人們覆蓋length()方法。另外,如果指定一個類為final,則該類所有的方法都是final。Java編譯器會尋找機會內聯(inline)所有的final方法(這和具體的編譯器實現有關)。此舉能夠使效能平均提高50%。 


6. 儘量使用區域性變數 

呼叫方法時傳遞的引數以及在呼叫中建立的臨時變數都儲存在棧(Stack)中,速度較快。其他變數,如靜態變數、例項變數等,都在堆(Heap)中建立,速度較慢。另外,依賴於具體的編譯器/JVM,區域性變數還可能得到進一步優化。 

7. 乘法和除法 

考慮下面的程式碼: 
for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; }  用移位操作替代乘法操作可以極大地提高效能。下面是修改後的程式碼:for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; } 


修改後的程式碼不再做乘以8的操作,而是改用等價的左移3位操作,每左移1位相當於乘以2。相應地,右移1位操作相當於除以2。值得一提的是,雖然移位操作速度快,但可能使程式碼比較難於理解,所以最好加上一些註釋。 
3. 選擇合適的引用機制 

在典型的JSP應用系統中,頁頭、頁尾部分往往被抽取出來,然後根據需要引入頁頭、頁尾。當前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動作。 

include指令:例如 
<%@ include file="copyright.html" %> 


該指令在編譯時引入指定的資源。在編譯之前,帶有include指令的頁面和指定的資源被合併成一個檔案。被引用的外部資源在編譯時就確定,比執行時才確定資源更高效。 

include動作:例如 
<jsp:include page="copyright.jsp" /> 


該動作引入指定頁面執行後生成的結果。由於它在執行時完成,因此對輸出結果的控制更加靈活。但時,只有當被引用的內容頻繁地改變時,或者在對主頁面的請求沒有出現之前,被引用的頁面無法確定時,使用include動作才合算。 

4. 在spring中對orm層的動作設定只讀屬性 

將 (只對資料庫進行讀取的操作) 設定只讀屬性 
10. Servlet與記憶體使用 

許多開發者隨意地把大量資訊儲存到使用者會話之中。一些時候,儲存在會話中的物件沒有及時地被垃圾回收機制回收。從效能上看,典型的症狀是使用者感到系統週期性地變慢,卻又不能把原因歸於任何一個具體的元件。如果監視JVM的堆空間,它的表現是記憶體佔用不正常地大起大落。解決這類記憶體問題主要有二種辦法。第一種辦法是,在所有作用範圍為會話的Bean中實現HttpSessionBindingListener介面。這樣,只要實現valueUnbound()方法,就可以顯式地釋放Bean使用的資源。 

另外一種辦法就是儘快地把會話作廢。大多數應用伺服器都有設定會話作廢間隔時間的選項。另外,也可以用程式設計的方式呼叫會話的setMaxInactiveInterval()方法,該方法用來設定在作廢會話之前,Servlet容器允許的客戶請求的最大間隔時間,以秒計算。 

11. HTTP Keep-Alive 

Keep-Alive功能使客戶端到伺服器端的連線持續有效,當出現對伺服器的後繼請求時,Keep-Alive功能避免了建立或者重新建立連線。市場上的大部分Web伺服器,包括iPlanet、IIS和Apache,都支援HTTP Keep-Alive。對於提供靜態內容的網站來說,這個功能通常很有用。但是,對於負擔較重的網站來說,這裡存在另外一個問題:雖然為客戶保留開啟的連線有一定的好處,但它同樣影響了效能,因為在處理暫停期間,本來可以釋放的資源仍舊被佔用。當Web伺服器和應用伺服器在同一臺機器上執行時,Keep-Alive功能對資源利用的影響尤其突出。 

12. JDBC與Unicode 

想必你已經瞭解一些使用JDBC時提高效能的措施,比如利用連線池、正確地選擇儲存過程和直接執行的SQL、從結果集刪除多餘的列、預先編譯SQL語句,等等。除了這些顯而易見的選擇之外,另一個提高效能的好選擇可能就是把所有的字元資料都儲存為Unicode(內碼表13488)。Java以Unicode形式處理所有資料,因此,資料庫驅動程式不必再執行轉換過程。但應該記住:如果採用這種方式,資料庫會變得更大,因為每個Unicode字元需要2個位元組儲存空間。另外,如果有其他非Unicode的程式訪問資料庫,效能問題仍舊會出現,因為這時資料庫驅動程式仍舊必須執行轉換過程。 
13. JDBC與I/O 

如果應用程式需要訪問一個規模很大的資料集,則應當考慮使用塊提取方式。預設情況下,JDBC每次提取32行資料。舉例來說,假設我們要遍歷一個5000行的記錄集,JDBC必須呼叫資料庫157次才能提取到全部資料。如果把塊大小改成512,則呼叫資料庫的次數將減少到10次。在一些情形下這種技術無效。例如,如果使用可滾動的記錄集,或者在查詢中指定了FOR UPDATE,則塊操作方式不再有效。 


Java程式碼優化--儘可能地使用stack(棧)變數(方法內部的區域性變數)            
Java程式包含了大量的物件,我們需要了解它們是從哪裡被訪問的,變數儲存於何處對程式的效能有顯著的影響--尤其是某些需要被頻繁訪問的變數。 
我們寫一個Java類,在其內部方法中定義的區域性變數或物件是儲存在stack(堆疊)中的,且JVM是一種stack-based的,因此訪問和操縱stack中的資料時效能最佳。而Java類的instance變數(這個類的field)和static變數是在constant pool(常量池)中儲存和得到訪問的。constant pool中儲存了所有的符號引用(symbolic references),指向所有型別(types)、值域(field),以及每個型別所使用的所有函式(mothods)。訪問instance和static變數時,由於它們存放於constant pool中,所以JVM需要使用更多更耗時的操作碼(分析程式生成的bytecode可以看出來)來訪問它們。 
下面給出一段程式碼示例,對比後說明怎麼儘可能地使用stack變數: 
package test; 
public class StackVars { 
    private int x;    // instance變數 
    private static int staticX; //static 變數 
    public void stackAccess(int val) {  //訪問和操作stack變數j 
        int j = 0; 
        for (int i = 0; i < val; i++) { 
            j += 1; 
        } 
    } 
    public void instanceAccess(int val) {//訪問和操作instance變數x 
        for (int i = 0; i < val; i++) { 
            x += 1; 
        } 
    } 
    public void staticAccess(int val) {//訪問和操作static變數staticX 
        for (int i = 0; i < val; i++) { 
            staticX += 1; 
        } 
    } 

經測試,發現執行instanceAccess()和staticAccess()方法的時間大約相同,但卻比執行stackAccess()方法慢了2~3倍。因此我們對instanceAccess()、staticAccess()兩個方法的程式碼作以下調整,以得到更快的效能: 
public void instanceAccess(int val) {//訪問和操作instance變數x 
        int tempX=x; 
        for (int i = 0; i < val; i++) { 
            tempX += 1; 
        } 
        x=tempX; 
    } 
    public void staticAccess(int val) {//訪問和操作static變數staticX 
        int tempStaticX=staticX; 
        for (int i = 0; i < val; i++) { 
            tempStaticX += 1; 
        } 
        staticX=tempStaticX; 
    } 
改善之處就是將instance和static變數放到迴圈之外,而用一個stack變數來完成多次區域性運算,最後再將這個stack變數的值傳回instance或static變數,從而提高了程式碼的效能。 



Sun JDK自帶JVM記憶體使用分析工具HProf 
      使用Sun JDK自帶JVM記憶體使用分析工具HProf可以分析JVM堆疊,從而找到佔用記憶體較大的物件。這對應經常出現記憶體洩漏(OOM)的JAVA系統進行調優很有幫助。 

HProf使用方法 
•         在WeblogicServer啟動指令碼中增加-Xrunhprof:heap=sites,重新啟動WeblogicServer。 
•         使用kill -3 <pid> 或退出WeblogicServer均會生成java.hprof.txt檔案,直接開啟此檔案便可分析JVM的具體執行情況。 

從java.hprof.txt記錄的JVM堆疊結果中可以發現JVM佔用記憶體較大的物件: 
          percent         live       alloc'ed  stack class 
rank   self  accum    bytes objs   bytes objs trace name 
    1  4.57%  4.57%  2289696 47702 8392224 174838  4251 [C 
    2  3.99%  8.57%  2000016    1 2000016    1 12308 [C 
    3  3.65% 12.22%  1827552 9622 1852672 10082 43265 [C 
    4  2.58% 14.80%  1293912 53913 3929424 163726  4258 java.lang.String 
    5  2.05% 16.85%  1028664 7585 3207272 24923  4252 [C 
    6  2.03% 18.88%  1015816  159 1015816  159 18694 [B 
    7  1.88% 20.77%   942080  230 2740224  669 20416 [B 
    8  1.61% 22.37%   805752 2142 2150856 4635 45318 [B 
    9  1.60% 23.98%   802880  772  802880  772 24710 weblogic.servlet.utils.URLMatchMap$URLMatchNode 
   10  1.60% 25.57%   799400 19985 2781400 69535 45073 cnc.util.Field 
   11  1.36% 26.93%   679360 3805  679360 3805   494 [B 
   12  1.35% 28.28%   674856 28119 5181240 215885  2985 java.util.HashMap$Entry 
…… 
…… 
   96  0.19% 63.73%    94776 3112   94776 3112  9146 [C 
   97  0.19% 63.92%    93456 3894  123936 5164 23631 java.lang.String 
   98  0.19% 64.10%    93224 3884  123968 5165 23644 java.lang.String 
   99  0.19% 64.29%    93192 3883  123936 5164 23636 java.lang.String 
  100  0.18% 64.47%    89528  238  240264  520 33227 [B 
  101  0.17% 64.64%    86448 1901  103472 2255 18715 java.lang.Object 
  102  0.17% 64.81%    85464  676   85768  695 18715 [S 
  103  0.17% 64.98%    85184 1331   85184 1331 28266 weblogic.ejb20.internal.MethodDescriptor 
  104  0.17% 65.15%    84224  752   84224  752 24148 weblogic.servlet.internal.dd.ServletDescriptor 
  105  0.17% 65.32%    84136  528 50471136 348769    63 [C 
  106  0.16% 65.48%    79968 1428  388976 6946  5503 java.lang.reflect.Method 
  107  0.15% 65.63%    77520 1615   77520 1615 27967 weblogic.ejb20.deployer.mbimpl.MethodInfoImpl 
  108  0.15% 65.79%    77056 4816  469808 29363 20250 java.lang.Object 
  109  0.15% 65.94%    76960   74   76960   74 23695 [B 
  110  0.15% 66.09%    76104 3171  215040 8960 45071 cnc.util.FyCol 
  111  0.15% 66.24%    74688 3112   74688 3112  9152 java.util.Hashtable$Entry 
  112  0.15% 66.39%    74688 3112   74688 3112  9147 java.lang.String 
  113  0.15% 66.54%    74280   61  794328  788 45313 [C 
  114  0.14% 66.68%    72480 1510  436032 9084 45353 [C 
  115  0.14% 66.82%    70720   68   70720   68 25869 [B 
  116  0.14% 66.97%    70720   68   70720   68 27448 [B 
  117  0.14% 67.11%    70272 1279  142672 2439  5503 [C 
  118  0.14% 67.24%    69256   86   69256   86  6584 [S 
  119  0.13% 67.38%    67056   66   67056   66 28882 java.lang.Object 
  120  0.13% 67.51%    66176  752   66176  752 24170 weblogic.servlet.internal.dd.UIDescriptor 
  121  0.13% 67.64%    65688  715   65688  715 25389 [C 
  122  0.13% 67.77%    65600    4  885600   54 23939 [C 
  123  0.13% 67.90%    65600    4  623200   38 40639 [C 
  124  0.13% 68.03%    65576  367   65576  367 51686 [C 
  125  0.13% 68.17%    65568    2   65568    2 30610 java.util.HashMap$Entry 
  126  0.13% 68.30%    65568    2  130816   16 43271 java.util.HashMap$Entry 
  127  0.13% 68.43%    65552    1   65552    1 16617 [B 
  128  0.13% 68.56%    64600 1615   64600 1615 27969 java.util.HashMap 
  129  0.13% 68.68%    63888 2662   64032 2668 16951 java.util.HashMap$Entry 
  130  0.13% 68.81%    63888 2662   64032 2668 16997 java.util.HashMap$Entry 
  131  0.13% 68.94%    63888 2662   64032 2668 16996 weblogic.rmi.internal.ClientMethodDescriptor 
  132  0.13% 69.07%    63888 2662   99120 4130 16949 java.lang.String 
  133  0.13% 69.19%    63888 2662   64032 2668 16976 java.lang.String 
  134  0.13% 69.32%    63232  152   63232  152  9655 weblogic.utils.collections.ConcurrentHashMap$Entry 
  135  0.13% 69.45%    63232  152   63232  152  9704 weblogic.utils.collections.ConcurrentHashMap$Entry 
  136  0.12% 69.57%    62168 3885   82632 5164 23628 [B 
  137  0.12% 69.69%    61680  406   66904  468     1 [C 
  138  0.12% 69.82%    61504    4  246016   16 47372 [B 
  139  0.12% 69.94%    61144   36 91019160 23904    92 [B 
  140  0.12% 70.06%    61040  763   61040  763 24194 weblogic.servlet.internal.dd.ServletMappingDescriptor 
  141  0.12% 70.18%    60400 1510  363360 9084 45338 java.util.Hashtable 
  142  0.12% 70.30%    59544  827   59544  827 24746 weblogic.servlet.internal.ServletRuntimeMBeanImpl 
  143  0.12% 70.42%    59248 1058  484984 8664 33236 oracle.jdbc.ttc7.TTCItem 
  144  0.12% 70.53%    58152  232  187176  764   748 [C 
  145  0.12% 70.65%    57888 2412  161904 6746 16621 java.lang.String 
  146  0.11% 70.77%    57400 1435   57400 1435 16855 java.util.HashMap 
…… 
…… 

根據以上的結果,在java.hprof.txt中定位到導致分配大記憶體的操作如下: 
TRACE 63: 
java.lang.StringBuffer.expandCapacity(StringBuffer.java:202) 
java.lang.StringBuffer.append(StringBuffer.java:401) 
java.util.zip.ZipFile.getEntry(ZipFile.java:148) 
java.util.jar.JarFile.getEntry(JarFile.java:198) 
TRACE 92: 
java.util.zip.InflaterInputStream.<init>(InflaterInputStream.java:71) 
java.util.zip.ZipFile$1.<init>(ZipFile.java:240) 
java.util.zip.ZipFile.getInputStream(ZipFile.java:212) 
java.util.zip.ZipFile.getInputStream(ZipFile.java:183) 
再進一步分析則需要應用開發人員對應用程式碼做相應的分析定位。 

注意:使用HProf非常消耗資源,切記不要在生產系統使用。 






Java優化程式設計(第二版) 
2.1 垃圾回收堆記憶體  
記憶體管理的話題在C或C++程式設計中討論得相對較多,因為在C與C++程式設計中需要開發人員自己申請並管理記憶體,開發人員可以申請/借用(Apply)系統記憶體並且負責釋放/歸還(Release)系統記憶體,如果“只借不還”就會造成系統記憶體洩露的問題。在Java程式設計中,這些工作由Java虛擬機器(JVM)負責處理。所有記憶體的申請、分配、釋放都由JVM負責完成。因此,開發人員就省去了這部分工作,不過這並不意味著開發人員可以完全依賴於JVM的記憶體管理功能,如果你這樣想並在實際的應用開發中也這樣做,你所開發的應用的效能,就有可能不是最優的。這是因為,無論配置多麼優良的硬體環境其自身資源都是有限的,由於沒有合理、科學的使用記憶體資源,即使是Java應用也會出現記憶體枯竭的現象。例如,我們經常會遇到的OutOfMemoryException。再者Java語言(其實不只Java語言)的效能極大程度上依賴於其執行的硬體環境資源,而記憶體又是硬體環境資源中重要的一部分,因此說,如果開發人員開發的Java應用沒能有效、合理地使用系統記憶體,那麼這個應用就不可能具備較高的效能,甚至會導致整個系統在執行一段時間後崩潰。本章將對Java應用開發中與記憶體管理相關的技術做詳細的講解。 
2.1  垃圾回收 
談到Java記憶體管理的話題,就必然會提到垃圾回收的概念,垃圾回收的英文名稱為Garbage Collection,簡稱GC,它是Java程式設計中有關記憶體管理的核心概念,Java虛擬機器(JVM)的記憶體管理機制被稱為垃圾回收機制。因此,要想掌握在開發Java應用時怎樣才能合理地管理記憶體,首先應該瞭解Java虛擬機器的記憶體管理機制——垃圾回收機制。否則,在不瞭解垃圾回收具體實現機制的情況下討論Java程式設計中的記憶體管理,優化Java應用效能,就有些紙上談兵,捨本逐末了。 
上面我們提到Java程式設計中的記憶體管理機制是通過垃圾回收來完成的,那麼在JVM執行環境中什麼樣的物件是垃圾呢?下面我們給出了在JVM執行環境中垃圾物件的定義: 
一個物件建立後被放置在JVM的堆記憶體(heap)中,當永遠不再引用這個物件時,它將被JVM在堆記憶體(heap)中回收。被建立的物件不能再生,同時也沒有辦法通過程式語句釋放它們。 
我們也可以這樣給JVM中的垃圾物件下定義: 
當物件在JVM執行空間中無法通過根集合(root set)到達(找到)時,這個物件就被稱為垃圾物件。根集合是由類中的靜態引用域與本地引用域組成的。JVM中通過根集合索引物件如圖2-1所示。 

圖2-1  JVM中通過根集合索引物件 
&注意  圖2-1中打了X標記的物件就是不可到達的物件,這些物件就被JVM視為垃圾物件並被JVM回收。JVM將給這些物件打上相應的標記,然後清掃回收這些物件,並將散碎的記憶體單元收集整合。 
這裡提到了堆記憶體的概念,它是JVM管理的一種記憶體型別,在做Java應用開發時經常會用到由JVM管理的兩種型別的記憶體:堆記憶體(heap)與棧記憶體(stack)。有關堆記憶體的概念,在前面的相關章節中,已經做過相應的介紹。簡單地講,堆記憶體主要用來儲存程式在執行時建立或例項化的物件與變數,例如:我們通過new MyClass()建立的類MyClass的物件。而棧記憶體(stack)則是用