Java 性能優化系列之1[設計與程序優化]

性能

通常來講,性能經過如下幾個方面來表現:java

  • 執行速度
  • 內存分配
  • 啓動時間
  • 負載承受能力

定量評測的性能指標:mysql

  • 執行時間
  • CPU時間
  • 內存分配
  • 磁盤吞吐量
  • 網絡吞吐量
  • 響應時間

調優的層面算法

  • 設計調優
  • 代碼調優
  • JVM調優
  • 數據庫調優
  • 操做系統調優

性能調優必須有明確的目標,不要爲了調優而調優,若是當前程序並無明顯的性能問題,盲目地進行調整,其風險可能遠遠大於收益。sql

 

設計優化

1. 單例模式數據庫

對於系統的關鍵組件和被頻繁使用的對象,使用單例模式能夠有效地改善系統的性能數組

 

2. 代理模式緩存

代理模式能夠用來實現延遲加載,從而提高系統的性能和反應速度。安全

 

另外,能夠考慮使用動態代理的方式 。 動態代理的方法有: JDK自帶的動態代理, CGLIB, Javassist, 或ASM庫。網絡

 

3. 享元模式數據結構

好處:

1) 能夠節省重複建立對象的開銷

2) 對系統內存的需求減小

 

4. 裝飾者模式

實現性能組件與功能組件的完美分離

 

5. 觀察者模式

觀察者模式能夠用於事件監聽、通知發佈等場合。能夠確保觀察者在不使用輪詢監控的狀況下,及時收到相關的消息和事件。

 

6. Value Object 模式

將一個對象的各個屬性進行封裝,將封裝後的對象在網絡中傳遞,從而使系統擁有更好的交互模型,而且減小網絡通訊數據,從而提升系統性能。

 

7. 業務代理模式

將一組由遠程方法調用構成的業務流程,封裝在一個位於展現層的代理類中。

 

思考:

單例模式, 工廠模式和享元模式的差別?

 

 

 

經常使用優化組件和方法

1.緩衝

I/O 操做很容易成爲性能瓶頸,因此,儘量在 I/O 讀寫中加入緩衝組件,以提升系統的性能。

2. 緩存

緩存能夠保存一些來之不易的數據或者計算結果,當須要再次使用這些數據時,能夠從緩存中低成本地獲取,而不須要再佔用寶貴的系統資源。

Java緩存框架:

EHCache, OSCache,JBossCache

3. 對象複用 -- "池"

最熟悉的線程池和數據庫鏈接池。

目前應用較爲普遍的數據庫鏈接池組件有C3P0 和Proxool.

4.並行替代串行

5. 負載均衡

跨JVM虛擬機,專門用於分佈式緩存的框架--Terracotta, 使用Terracotta能夠實現Tomcat的Session共享。

6. 時間換空間

7. 空間換時間

 

 

 

程序優化

1. 字符串優化處理

1)

 

1 [java] view plain copy
2  
3 String str1 ="abc";  
4 String str2 ="abc";  
5 String str3 = new String("abc");  
6   
7 System.out.println(str1==str2);  //true  
8 System.out.println(str1==str3);  //false  
9 System.out.println(str1==str3.intern()); //true  

 

2) subString() 方法的內存泄漏

 

若是原字串很長,截取的字串卻有比較短,使用如下方式返回:

 

1 [java] view plain copy
2  
3 new String(str1.substring(begin,end));  

 

3) 字符串分割和查找

 

可使用的方法:

split()方法   -- 最慢, 寫法簡單

StringTokenizer 方法  -- 較快,寫法通常

indexOf()和subString() 方法 - 最快, 寫法麻煩

 

 1 [java] view plain copy
 2  
 3 package performance.program.string;  
 4   
 5 import java.util.StringTokenizer;  
 6   
 7 public class StringSplit {  
 8   
 9     public static void splitMethod(String str) {  
10   
11         long beginTime = System.currentTimeMillis();  
12         for (int i = 0; i < 10000; i++) {  
13             str.split(";");  
14         }  
15   
16         long endTime = System.currentTimeMillis();  
17         System.out.println("splitMethod use " + (endTime - beginTime));  
18     }  
19   
20     public static void tokenizerMethod(String str) {  
21   
22         long beginTime = System.currentTimeMillis();  
23   
24         StringTokenizer st = new StringTokenizer(str, ";");  
25         for (int i = 0; i < 10000; i++) {  
26             while (st.hasMoreTokens()) {  
27                 st.nextToken();  
28             }  
29             st = new StringTokenizer(str, ";");  
30         }  
31   
32         long endTime = System.currentTimeMillis();  
33         System.out.println("tokenizerMethod use " + (endTime - beginTime));  
34     }  
35   
36     public static void IndexMethod(String str) {  
37   
38         long beginTime = System.currentTimeMillis();  
39         String tmp = str;  
40         for (int i = 0; i < 10000; i++) {  
41             while (true) {  
42                 String splitStr = null;  
43                 int j = tmp.indexOf(";");  
44                 if(j<0) break;  
45                     splitStr = tmp.substring(0,j);  
46                 tmp = tmp.substring(j+1);  
47             }  
48             tmp = str;  
49         }  
50   
51         long endTime = System.currentTimeMillis();  
52         System.out.println("IndexMethod use " + (endTime - beginTime));  
53     }  
54   
55     /** 
56      * @param args 
57      */  
58     public static void main(String[] args) {  
59         // TODO Auto-generated method stub  
60         String orgStr = null;  
61         StringBuffer sb = new StringBuffer();  
62         for (int i = 0; i < 1000; i++) {  
63             sb.append(i);  
64             sb.append(";");  
65         }  
66         orgStr = sb.toString();  
67         splitMethod(orgStr);  
68         tokenizerMethod(orgStr);  
69         IndexMethod(orgStr);  
70   
71     }  
72   
73 }  

 


4) 使用ChartAt 代替  startsWith 和 endsWith

 

性能要求比較高時,可使用這條。

5) StringBuffer 和 StringBuilder

String result = "String" + "and" + "string"+"append";

這段看起來性能不高的代碼在實際執行時反而會比StringBuilder 來的快。

緣由是Java 編譯器自己會作優化。

可是不能徹底依靠編譯器的優化, 仍是建議顯示地使用StringBuffer 或StringBuffer對象來提高系統性能。

StringBuffer 和 StringBuilder 的差別是:

StingBuffer 幾乎全部的方法都作了同步

StringBuilder 並無任何同步。

因此StringBuilder 的效率好於StringBuffer, 可是在多線程系統中,StringBuilder 沒法保證線程安全。

另外,預先評估StringBuilder 的大小,能提高系統的性能。

 

2. 核心數據結構

1) List 接口

3種List實現: ArrayList,  Vector, 和LinkedList

ArrayList 和Vector 使用了數組實現, Vector 絕大部分方法都作了線程同步, ArrayList 沒有對任何一個方法作線程同步。(ArrayList 和Vector 看上去性能相差無幾)

使用LinkedList 對堆內存和GC的要求更高。

若是在系統應用中, List對象須要常常在任意位置插入元素,則能夠考慮使用LinkedList 替代ArrayList

對於ArrayList從尾部刪除元素時效率很高,從頭部刪除元素時至關費時,

而LinkedList 從頭尾刪除元素時效率相差無幾,可是從中間刪除元素時性能很是槽糕。

 

  頭部 中間 尾部
ArrayList 6203 3125 16
LinkedList 15 8781 16

 

 

List 遍歷操做

 

  ForEach 迭代器 for 循環
ArrayList 63 47 31
LinkedList 63 47 無窮大

 

 

2) Map 接口

實現類有: Hashtable, HashMap, LinkedHashMap和TreeMap

HashTable 和HashMap  的差別

HashTable大部分方法作了同步, HashTable 不容許key或者Value 使用null值;(性能上看起來無明顯差別)

 

3) Set 接口

 

4) 優化集合訪問代碼

1. 分離循環中被重複調用的代碼

for(int i=0;i<collection.size();i++)

替換成

int  count = collection.size();

for(int i=0;i<count;i++)

 

5). RandomAccess接口

 

3. 使用NIO 提高性能

在Java 的標準I/O中, 提供了基於流的I/O 實現, 即InputStream 和 OutputStream. 這種基於流的實現是以字節爲單位處理數據, 而且很是容易創建各類過濾器。

NIO是 New I/O 的簡稱, 與舊式的基於流的 I/O 方法相對, 它表示新的一套Java I/O 標準。是在java 1.4 中被歸入到JDK中的, 特性有

- 爲全部的原始類型提供 Buffer 緩存支持

-  使用Java.nio.charset.Charset 做爲字符集編碼解碼解決方案

- 增長通道對象(Channel), 做爲新的原始I/O 抽象

- 支持鎖和內存映射文件的文件訪問接口

- 提供了基於Selector 的異步網絡I/O

與流式的I/O 不一樣, NIO 是基於塊(Block 的), 它以塊爲基本單位處理數據。

 

 

 

4. 引用類型。

強引用、軟引用、弱引用和虛引用

強引用:

a)強引用能夠直接訪問目標對象

b) 強引用所指向的對象在任什麼時候候都不會被系統回收

c)強引用可能致使內存泄漏

 

軟引用:

軟引用能夠經過java.lang.ref.SoftReference來使用。 一個持有軟引用的對象,不會被JVM很快回收, JVM會根據當前的使用情況來判斷什麼時候回收.當堆使用率臨近閾值時,纔會去回收軟引用的對象。只要有足夠的內存,軟引用即可能在內存中存活相對長一段時間。所以,軟引用能夠用於實現對內存敏感的Cache.

 

弱引用:

在系統GC時,只要發現弱引用,無論系統堆空間是否足夠,都會將對象進行回收。

 

虛引用

一個持有虛引用的對象,和沒有引用幾乎是同樣的,隨時均可能被垃圾回收器回收。但試圖經過虛引用的get()方法去跌強引用時,老是會失敗。而且,虛引用必須和引用隊列一塊兒使用,它的做用在於跟蹤回收過程。

 

WeakHashMap類及其實現

WeakHashMap 是弱引用的一種典型應用,它能夠做爲簡單的緩存表解決方案。

 

改善系統性能的技巧

1. 慎用異常

try catch 應用與循環體以外

 

2. 使用局部變量

調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧中,速度較快。

其餘變量,如靜態變量、實例變量等,都在堆中建立,速度較慢。

局部變量的訪問速度遠遠高於類的成員變量。

 

3. 位運算代替乘除法

 

4. 替換Switch .

這一條應該注意的是一個新的思路問題, 使用不一樣的方式代替switch 語句。

這裏的例子性能測試起來, switch 的性能反而更好一些。

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class SwitchReplaceSkill {  
 6   
 7     /** 
 8      * @param args 
 9      */  
10   
11     protected void oldMethod() {  
12         int re = 0;  
13         for (int i = 0; i < 100000000; i++) {  
14             re = switchInt(i);  
15         }  
16     }  
17   
18     public void newMethod() {  
19         int re=0;  
20         int[] sw= new int[]{0,3,6,7,8,10,16,18,44};  
21         for(int i = 0; i < 100000000; i++) {  
22             re = arrayInt(sw,i);  
23         }  
24     }  
25   
26     protected int switchInt(int z) {  
27         int i = z % 10 + 1;  
28         switch (i) {  
29         case 1:  
30             return 3;  
31         case 2:  
32             return 6;  
33         case 3:  
34             return 7;  
35         case 4:  
36             return 8;  
37         case 5:  
38             return 10;  
39         case 6:  
40             return 16;  
41         case 7:  
42             return 18;  
43         case 8:  
44             return 44;  
45         default:  
46             return -1;  
47         }  
48     }  
49     protected int arrayInt(int[] sw,int z)  
50     {  
51         int i=z%10+1;  
52         if(i>8||i<1)  
53         {  
54             return -1;  
55         }else{  
56             return sw[i];  
57         }  
58     }  
59   
60     public static void main(String[] args) {  
61         // TODO Auto-generated method stub  
62         SwitchReplaceSkill skill = new SwitchReplaceSkill();  
63         long begTime = System.currentTimeMillis();        
64         skill.oldMethod();  
65         long endTime = System.currentTimeMillis();        
66         System.out.println("Old Method use: "+(endTime-begTime));  
67           
68         begTime = System.currentTimeMillis();     
69         skill.newMethod();  
70         endTime = System.currentTimeMillis();     
71         System.out.println("New Method use: "+(endTime-begTime));  
72           
73     }  
74   
75 }  

 


5. 一維數組代替二維數組

 

直接看例子:

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class OneDime2TwoDime {  
 6   
 7     protected void oldMethod(){  
 8         int[][] array = new int[1000][1000];  
 9         int re = 0;  
10         for(int k=0;k<100;k++)  
11             for(int i=0;i<array[0].length;i++)  
12                 for(int j=0;j<array[0].length;j++)  
13                     array[i][j] = i;  
14           
15         for(int k=0;k<100;k++)  
16             for(int i=0;i<array[0].length;i++)  
17                 for(int j=0;j<array[0].length;j++)  
18                     re=array[i][j];       
19           
20     }  
21       
22     protected void newMethod(){  
23         int[] array = new int[1000000];  
24         int re = 0;  
25         for(int k=0;k<100;k++)  
26             for(int i=0;i<array.length;i++)  
27                 array[i] = i;  
28         for(int k=0;k<100;k++)  
29             for(int i=0;i<array.length;i++)  
30                 re = array[i];    
31     }     
32     /** 
33      * @param args 
34      */  
35     public static void main(String[] args) {  
36         // TODO Auto-generated method stub  
37         OneDime2TwoDime skill = new OneDime2TwoDime();  
38         long begTime = System.currentTimeMillis();        
39         skill.oldMethod();  
40         long endTime = System.currentTimeMillis();        
41         System.out.println("Old Method use: "+(endTime-begTime));  
42           
43         begTime = System.currentTimeMillis();     
44         skill.newMethod();  
45         endTime = System.currentTimeMillis();     
46         System.out.println("New Method use: "+(endTime-begTime));  
47     }  
48   
49 }  
50 Old Method use: 453
51 New Method use: 281

 

 

 

一維數字用的時間少了不少。

 

6. 提取表達式

有些重複運算的部分能夠提取出來。

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class ExtraExpression {  
 6   
 7     protected void oldMethod(){  
 8         double d = Math.random();  
 9         double a = Math.random();  
10         double b = Math.random();  
11         double e = Math.random();  
12         double x,y;  
13         for(int i=0;i<10000000;i++)  
14         {  
15             x = d*a*b/3*4*a;  
16             x = e*a*b/3*4*a;  
17         }  
18     }  
19       
20     protected void newMethod(){  
21         double d = Math.random();  
22         double a = Math.random();  
23         double b = Math.random();  
24         double e = Math.random();  
25         double x,y,t;  
26         for(int i=0;i<10000000;i++)  
27         {  
28             t = a*b/3*4*a;  
29             x = d*t;  
30             x = e*t;  
31         }  
32     }  
33       
34     /** 
35      * @param args 
36      */  
37     public static void main(String[] args) {  
38         // TODO Auto-generated method stub  
39         OneDime2TwoDime skill = new OneDime2TwoDime();  
40         long begTime = System.currentTimeMillis();        
41         skill.oldMethod();  
42         long endTime = System.currentTimeMillis();        
43         System.out.println("Old Method use: "+(endTime-begTime));  
44           
45         begTime = System.currentTimeMillis();     
46         skill.newMethod();  
47         endTime = System.currentTimeMillis();     
48         System.out.println("New Method use: "+(endTime-begTime));  
49     }  
50   
51 }  
52 
53 Old Method use: 109
54 New Method use: 79

 

 

 

7.  展開循環

展開循環是一種在極端狀況下使用的優化手段,由於展開循可能會影響代碼的可讀性和可維護性, 因此取捨之間, 就要根據實際情況來看了。

看例子:

 

[java] view plain copy
 
package com.oscar999.performance.skill;  
  
public class ExpandCycle {  
    protected void oldMethod(){  
        int[] array = new int[9999999];  
        for(int i=0;i<9999999;i++){  
            array[i]=i;  
        }  
    }  
      
    protected void newMethod(){  
        int[] array = new int[9999999];  
        for(int i=0;i<9999999;i+=3){  
            array[i]=i;  
            array[i+1]=i+1;  
            array[i+2]=i+2;  
        }  
    }  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        ExpandCycle skill = new ExpandCycle();  
        long begTime = System.currentTimeMillis();        
        skill.oldMethod();  
        long endTime = System.currentTimeMillis();        
        System.out.println("Old Method use: "+(endTime-begTime));  
          
        begTime = System.currentTimeMillis();     
        skill.newMethod();  
        endTime = System.currentTimeMillis();     
        System.out.println("New Method use: "+(endTime-begTime));  
    }  
  
}  
Old Method use: 78
New Method use: 47

 

 

 

8. 布爾運算代替位運算

雖然位運算的速度遠遠高於算術運算,可是在條件判斷時,使用位運算替代布爾運算倒是很是錯誤的選擇。

對於"a&&b&&c", 只要有一項返回 false, 整個表達式就返回 false.

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class BooleanBit {  
 6     protected void oldMethod(){  
 7         boolean a = false;  
 8         boolean b = true;  
 9         int d = 0;  
10         for(int i=0;i<10000000;i++)  
11             if(a&b&"Java_Perform".contains("Java"))  
12                 d = 0;  
13     }  
14       
15     protected void newMethod(){  
16         boolean a = false;  
17         boolean b = true;  
18         int d = 0;  
19         for(int i=0;i<10000000;i++)  
20             if(a&&b&&"Java_Perform".contains("Java"))  
21                 d = 0;        
22     }  
23       
24     /** 
25      * @param args 
26      */  
27     public static void main(String[] args) {  
28         // TODO Auto-generated method stub  
29         BooleanBit skill = new BooleanBit();  
30         long begTime = System.currentTimeMillis();        
31         skill.oldMethod();  
32         long endTime = System.currentTimeMillis();        
33         System.out.println("Old Method use: "+(endTime-begTime));  
34           
35         begTime = System.currentTimeMillis();     
36         skill.newMethod();  
37         endTime = System.currentTimeMillis();     
38         System.out.println("New Method use: "+(endTime-begTime));  
39     }  
40   
41 }  
42 
43 Old Method use: 265
44 New Method use: 32

 

 

9. 使用 arrayCopy()

Java API 提升了數組複製的高效方法: arrayCopy().

這個函數是 native 函數, 一般native 函數的性能要優於普通的函數, 僅出於性能考慮, 在軟件開發時, 應儘量調用native 函數。

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class ArrayCopy {  
 6     protected void oldMethod(){  
 7         int size = 100000;  
 8         int[] array = new int[size];  
 9         int[] arraydst = new int[size];  
10         for(int i=0;i<array.length;i++)  
11         {  
12             array[i]=i;  
13         }  
14         for(int k=0;k<1000;k++)  
15             for(int i=0;i<size;i++)  
16                 arraydst[i]=array[i];  
17     }  
18       
19     protected void newMethod(){  
20         int size = 100000;  
21         int[] array = new int[size];  
22         int[] arraydst = new int[size];  
23         for(int i=0;i<array.length;i++)  
24         {  
25             array[i]=i;  
26         }  
27         for(int k=0;k<1000;k++)  
28             System.arraycopy(array, 0, arraydst, 0, size);  
29     }  
30       
31     /** 
32      * @param args 
33      */  
34     public static void main(String[] args) {  
35         // TODO Auto-generated method stub  
36         ArrayCopy skill = new ArrayCopy();  
37         long begTime = System.currentTimeMillis();        
38         skill.oldMethod();  
39         long endTime = System.currentTimeMillis();        
40         System.out.println("Old Method use: "+(endTime-begTime));  
41           
42         begTime = System.currentTimeMillis();     
43         skill.newMethod();  
44         endTime = System.currentTimeMillis();     
45         System.out.println("New Method use: "+(endTime-begTime));  
46     }  
47   
48   
49 }  
50 Old Method use: 156
51 New Method use: 63

 

 

 

10. 使用 Buffer 進行 I/O 操做

除NIO 外, 使用Java 進行I/O 操做有兩種基本方式

1. 使用基於InputStream 和 OutoutStream的方式

2. 使用Writer 和Reader

不管使用哪一種方式進行文件I/O , 若是能合理地使用緩衝, 就能有效提升I/O 的性能

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 import java.io.BufferedOutputStream;  
 6 import java.io.DataOutputStream;  
 7 import java.io.FileOutputStream;  
 8   
 9 public class BufferStream {  
10   
11     protected void oldMethod() {  
12         int count = 10000;  
13         try {  
14             DataOutputStream dos = new DataOutputStream(new FileOutputStream(  
15                     "testfile.txt"));  
16             for (int i = 0; i < count; i++)  
17                 dos.writeBytes(String.valueOf(i) + "\r\n");  
18             dos.close();  
19   
20         } catch (Exception e) {  
21             // TODO Auto-generated catch block  
22             e.printStackTrace();  
23         }  
24     }  
25   
26     protected void newMethod(){  
27         int count = 10000;  
28         try{  
29             DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("testfile.txt")));      
30             for (int i = 0; i < count; i++)  
31                 dos.writeBytes(String.valueOf(i) + "\r\n");  
32             dos.close();  
33         }catch(Exception e)  
34         {  
35             e.printStackTrace();  
36         }  
37           
38     }  
39   
40     /** 
41      * @param args 
42      */  
43     public static void main(String[] args) {  
44         // TODO Auto-generated method stub  
45         BufferStream skill = new BufferStream();  
46         long begTime = System.currentTimeMillis();  
47         skill.oldMethod();  
48         long endTime = System.currentTimeMillis();  
49         System.out.println("Old Method use: " + (endTime - begTime));  
50   
51         begTime = System.currentTimeMillis();  
52         skill.newMethod();  
53         endTime = System.currentTimeMillis();  
54         System.out.println("New Method use: " + (endTime - begTime));  
55     }  
56   
57 }  
58 
59 Old Method use: 516
60 New Method use: 0

 

 

 

11. 使用clone() 代替new 

對於重量級對象, 優於對象在構造函數中可能會進行一些複雜且耗時額操做, 所以, 構造函數的執行時間可能會比較長。Object.clone() 方法能夠繞過對象構造函數, 快速複製一個對象實例。因爲不須要調用對象構造函數, 所以, clone 方法不會受到構造函數性能的影響, 快速生成一個實例。

 

12. 靜態方法替代實例方法

使用static 關鍵字描述的方法爲靜態方法, 在Java 中, 優於實例方法須要維護一張相似虛函數表的結構,以實現對多態的支持。 與靜態方法相比, 實例方法的調用須要更多的資源。 所以,對於一些經常使用的工具類方法,沒有對其進行重載的必要,那麼將它們聲明爲static, 即可以加速方法的調用。

相關文章
相關標籤/搜索