Java 效能最佳化系列之1[設計與程式最佳化]

來源:互聯網
上載者:User

標籤:

效能

一般來說,效能通過以下幾個方面來表現:

  • 執行速度
  • 記憶體配置
  • 啟動時間
  • 負載承受能力

定量評測的效能指標:

  • 執行時間
  • CPU時間
  • 記憶體配置
  • 磁碟輸送量
  • 網路輸送量
  • 回應時間

調優的層面

  • 設計調優
  • 代碼調優
  • JVM調優
  • 資料庫調優
  • 作業系統調優

效能調優必須有明確的目標,不要為了調優而調優,如果當前程式並沒有明顯的效能問題,盲目地進行調整,其風險可能遠遠大於收益。


設計最佳化

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)

String str1 ="abc";String str2 ="abc";String str3 = new String("abc");System.out.println(str1==str2);  //trueSystem.out.println(str1==str3);  //falseSystem.out.println(str1==str3.intern()); //true
2) subString() 方法的記憶體流失

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

new String(str1.substring(begin,end));
3) 字串分割和尋找

可以使用的方法:

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

StringTokenizer 方法  -- 較快,寫法一般

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

package performance.program.string;import java.util.StringTokenizer;public class StringSplit {public static void splitMethod(String str) {long beginTime = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {str.split(";");}long endTime = System.currentTimeMillis();System.out.println("splitMethod use " + (endTime - beginTime));}public static void tokenizerMethod(String str) {long beginTime = System.currentTimeMillis();StringTokenizer st = new StringTokenizer(str, ";");for (int i = 0; i < 10000; i++) {while (st.hasMoreTokens()) {st.nextToken();}st = new StringTokenizer(str, ";");}long endTime = System.currentTimeMillis();System.out.println("tokenizerMethod use " + (endTime - beginTime));}public static void IndexMethod(String str) {long beginTime = System.currentTimeMillis();String tmp = str;for (int i = 0; i < 10000; i++) {while (true) {String splitStr = null;int j = tmp.indexOf(";");if(j<0) break;splitStr = tmp.substring(0,j);tmp = tmp.substring(j+1);}tmp = str;}long endTime = System.currentTimeMillis();System.out.println("IndexMethod use " + (endTime - beginTime));}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubString orgStr = null;StringBuffer sb = new StringBuffer();for (int i = 0; i < 1000; i++) {sb.append(i);sb.append(";");}orgStr = sb.toString();splitMethod(orgStr);tokenizerMethod(orgStr);IndexMethod(orgStr);}}

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 的效能反而更好一些。

package com.oscar999.performance.skill;public class SwitchReplaceSkill {/** * @param args */protected void oldMethod() {int re = 0;for (int i = 0; i < 100000000; i++) {re = switchInt(i);}}public void newMethod() {int re=0;int[] sw= new int[]{0,3,6,7,8,10,16,18,44};for(int i = 0; i < 100000000; i++) {re = arrayInt(sw,i);}}protected int switchInt(int z) {int i = z % 10 + 1;switch (i) {case 1:return 3;case 2:return 6;case 3:return 7;case 4:return 8;case 5:return 10;case 6:return 16;case 7:return 18;case 8:return 44;default:return -1;}}protected int arrayInt(int[] sw,int z){int i=z%10+1;if(i>8||i<1){return -1;}else{return sw[i];}}public static void main(String[] args) {// TODO Auto-generated method stubSwitchReplaceSkill skill = new SwitchReplaceSkill();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));}}

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

直接看例子:

package com.oscar999.performance.skill;public class OneDime2TwoDime {protected void oldMethod(){int[][] array = new int[1000][1000];int re = 0;for(int k=0;k<100;k++)    for(int i=0;i<array[0].length;i++)    for(int j=0;j<array[0].length;j++)    array[i][j] = i;for(int k=0;k<100;k++)    for(int i=0;i<array[0].length;i++)    for(int j=0;j<array[0].length;j++)    re=array[i][j];}protected void newMethod(){int[] array = new int[1000000];int re = 0;for(int k=0;k<100;k++)for(int i=0;i<array.length;i++)array[i] = i;for(int k=0;k<100;k++)for(int i=0;i<array.length;i++)re = array[i];}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubOneDime2TwoDime skill = new OneDime2TwoDime();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: 453
New Method use: 281

一維數字用的時間少了很多。


6. 提取運算式

有些重複運算的部分可以提取出來。

package com.oscar999.performance.skill;public class ExtraExpression {protected void oldMethod(){double d = Math.random();double a = Math.random();double b = Math.random();double e = Math.random();double x,y;for(int i=0;i<10000000;i++){x = d*a*b/3*4*a;x = e*a*b/3*4*a;}}protected void newMethod(){double d = Math.random();double a = Math.random();double b = Math.random();double e = Math.random();double x,y,t;for(int i=0;i<10000000;i++){t = a*b/3*4*a;x = d*t;x = e*t;}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubOneDime2TwoDime skill = new OneDime2TwoDime();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: 109
New Method use: 79


7.  展開迴圈

展開迴圈是一種在極端情況下使用的最佳化手段,因為展開循可能會影響代碼的可讀性和可維護性, 所以取捨之間, 就要根據實際狀況來看了。

看例子:

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 stubExpandCycle 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.

package com.oscar999.performance.skill;public class BooleanBit {protected void oldMethod(){boolean a = false;boolean b = true;int d = 0;for(int i=0;i<10000000;i++)if(a&b&"Java_Perform".contains("Java"))d = 0;}protected void newMethod(){boolean a = false;boolean b = true;int d = 0;for(int i=0;i<10000000;i++)if(a&&b&&"Java_Perform".contains("Java"))d = 0;}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubBooleanBit skill = new BooleanBit();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: 265
New Method use: 32

9. 使用 arrayCopy()

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

這個函數是 native 函數, 通常native 函數的效能要優於普通的函數, 僅出於效能考慮, 在軟體開發時, 應儘可能調用native 函數。

package com.oscar999.performance.skill;public class ArrayCopy {protected void oldMethod(){int size = 100000;int[] array = new int[size];int[] arraydst = new int[size];for(int i=0;i<array.length;i++){array[i]=i;}for(int k=0;k<1000;k++)for(int i=0;i<size;i++)arraydst[i]=array[i];}protected void newMethod(){int size = 100000;int[] array = new int[size];int[] arraydst = new int[size];for(int i=0;i<array.length;i++){array[i]=i;}for(int k=0;k<1000;k++)System.arraycopy(array, 0, arraydst, 0, size);}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubArrayCopy skill = new ArrayCopy();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: 156
New Method use: 63


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

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

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

2. 使用Writer 和Reader

無論使用哪種方式進行檔案I/O , 如果能合理地使用緩衝, 就能有效提高I/O 的效能


package com.oscar999.performance.skill;import java.io.BufferedOutputStream;import java.io.DataOutputStream;import java.io.FileOutputStream;public class BufferStream {protected void oldMethod() {int count = 10000;try {DataOutputStream dos = new DataOutputStream(new FileOutputStream("testfile.txt"));for (int i = 0; i < count; i++)dos.writeBytes(String.valueOf(i) + "\r\n");dos.close();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}protected void newMethod(){int count = 10000;try{DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("testfile.txt")));for (int i = 0; i < count; i++)dos.writeBytes(String.valueOf(i) + "\r\n");dos.close();}catch(Exception e){e.printStackTrace();}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubBufferStream skill = new BufferStream();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: 516
New Method use: 0


11. 使用clone() 代替new 

對於重量級對象, 優於對象在建構函式中可能會進行一些複雜且耗時額操作, 因此, 建構函式的執行時間可能會比較長。Object.clone() 方法可以繞過物件建構函數, 快速複製一個對象執行個體。由於不需要調用物件建構函數, 因此, clone 方法不會受到建構函式效能的影響, 快速產生一個執行個體。


12. 靜態方法替代執行個體方法

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



著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Java 效能最佳化系列之1[設計與程式最佳化]

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.