Java中提升效能對程式碼作的建議(轉Mark)

qq_36821448發表於2018-03-28

下面是參考網路資源總結的一些在Java程式設計中儘可能要做到的一些地方。

  1. 儘量在合適的場合使用單例

  使用單例可以減輕載入的負擔,縮短載入的時間,提高載入的效率,但並不是所有地方都適用於單例,簡單來說,單例主要適用於以下三個方面:

  第一,控制資源的使用,通過執行緒同步來控制資源的併發訪問;

  第二,控制例項的產生,以達到節約資源的目的;

  第三,控制資料共享,在不建立直接關聯的條件下,讓多個不相關的程式或執行緒之間實現通訊。

  2. 儘量避免隨意使用靜態變數

  要知道,當某個物件被定義為stataic變數所引用,那麼gc通常是不會回收這個物件所佔有的記憶體,如

public class A{

  static B b = new B();

  }

  此時靜態變數b的生命週期與A類同步,如果A類不會解除安裝,那麼b物件會常駐記憶體,直到程式終止。

  3. 儘量避免過多過常的建立Java物件

  儘量避免在經常呼叫的方法,迴圈中new物件,由於系統不僅要花費時間來建立物件,而且還要花時間對這些物件進行垃圾回收和處理,在我們可以控制的範圍內,最大限度的重用物件,最好能用基本的資料型別或陣列來替代物件。

  4. 儘量使用final修飾符

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

  5. 儘量使用區域性變數

  呼叫方法時傳遞的引數以及在呼叫中建立的臨時變數都儲存在棧(Stack)中,速度較快。其他變數,如靜態變數、例項變數等,都在堆(Heap)中建立,速度較慢。

  6. 儘量處理好包裝型別和基本型別兩者的使用場所

  雖然包裝型別和基本型別在使用過程中是可以相互轉換,但它們兩者所產生的記憶體區域是完全不同的,基本型別資料產生和處理都在棧中處理,包裝型別是物件,是在堆中產生例項。

  在集合類物件,有物件方面需要的處理適用包裝型別,其他的處理提倡使用基本型別。

  7. 慎用synchronized,儘量減小synchronize的方法

  都知道,實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。synchronize方法被呼叫時,直接會 把當前物件鎖  了,在方法執行完之前其他執行緒無法呼叫當前物件的其他方法。所以synchronize的方法儘量小,並且應儘量使用方法同步代替程式碼塊同步。

  8. 儘量使用StringBuilder和StringBuffer進行字串連線

  這個就不多講了。

  9. 儘量不要使用finalize方法

  實際上,將資源清理放在finalize方法中完成是非常不好的選擇,由於GC的工作量很大,尤其是回收Young代記憶體時,大都會引起應用程式暫停,所以再選擇使用finalize方法進行資源清理,會導致GC負擔更大,程式執行效率更差。

  10. 儘量使用基本資料型別代替物件

String str = "hello";

  上面這種方式會建立一個"hello"字串,而且JVM的字元快取池還會快取這個字串;

String str = new String("hello");

  此時程式除建立字串外,str所引用的String物件底層還包含一個char[]陣列,這個char[]陣列依次存放了h,e,l,l,o

  11. 單執行緒應儘量使用HashMap、ArrayList

  HashTable、Vector等使用了同步機制,降低了效能。

  12. 儘量合理的建立HashMap

  當你要建立一個比較大的hashMap時,充分利用另一個建構函式

public HashMap(int initialCapacity, float loadFactor)

  避免HashMap多次進行了hash重構,擴容是一件很耗費效能的事,在預設中initialCapacity只有16,而 loadFactor是 0.75,需要多大的容量,你最好能準確的估計你所需要的最佳大小,同樣的Hashtable,Vectors也是一樣的道理。

  13. 儘量減少對變數的重複計算

  如

  for(int i=0;i 

  應該改為

  for(int i=0,len=list.size();i 

  並且在迴圈中應該避免使用複雜的表示式,在迴圈中,迴圈條件會被反覆計算,如果不使用複雜表示式,而使迴圈條件值不變的話,程式將會執行的更快。

  14. 儘量避免不必要的建立

  如

 

 A a = new A();

  if(i==1){list.add(a);}

  應該改為

  if(i==1){

  A a = new A();

  list.add(a);}

  15. 儘量在finally塊中釋放資源

  程式中使用到的資源應當被釋放,以避免資源洩漏。這最好在finally塊中去做。不管程式執行的結果如何,finally塊總是會執行的,以確保資源的正確關閉。

  16. 儘量使用移位來代替'a/b'的操作

  "/"是一個代價很高的操作,使用移位的操作將會更快和更有效

  如

  int num = a / 4;

  int num = a / 8;

  應該改為

  int num = a 》 2;

  int num = a 》 3;

  但注意的是使用移位應新增註釋,因為移位操作不直觀,比較難理解

  17.儘量使用移位來代替'a*b'的操作

  同樣的,對於'*'操作,使用移位的操作將會更快和更有效

  如

  int num = a * 4;

  int num = a * 8;

  應該改為

  int num = a 《 2;

  int num = a 《 3;

  18. 儘量確定StringBuffer的容量

  StringBuffer  的構造器會建立一個預設大小(通常是16)的字元陣列。在使用中,如果超出這個大小,就會重新分配記憶體,建立一個更大的陣列,並將原先的陣列複製過來,再  丟棄舊的陣列。在大多數情況下,你可以在建立 StringBuffer的時候指定大小,這樣就避免了在容量不夠的時候自動增長,以提高效能。

  如:StringBuffer buffer = new StringBuffer(1000);

  19. 儘量早釋放無用物件的引用

  大部分時,方法區域性引用變數所引用的物件 會隨著方法結束而變成垃圾,因此,大部分時候程式無需將區域性,引用變數顯式設為null.

  例如: 

Public void test(){

  Object obj = new Object();

  ……

  Obj=null;

  }

  上面這個就沒必要了,隨著方法test()的執行完成,程式中obj引用變數的作用域就結束了。但是如果是改成下面:

 Public void test(){

  Object obj = new Object();

  ……

  Obj=null;

  //執行耗時,耗記憶體操作;或呼叫耗時,耗記憶體的方法

  ……

  }

  這時候就有必要將obj賦值為null,可以儘早的釋放對Object物件的引用。

  20. 儘量避免使用二維陣列

  二維資料佔用的記憶體空間比一維陣列多得多,大概10倍以上。

  21. 儘量避免使用split

  除非是必須的,否則應該避免使用split,split由於支援正規表示式,所以效率比較低,如果是頻繁的幾十,幾百萬的呼叫將會耗費大量資 源,如果確實需  要頻繁的呼叫split,可以考慮使用apache的StringUtils.split(string,char),頻繁split的可以快取結果。

  22. ArrayList & LinkedList

  一  個是線性表,一個是連結串列,一句話,隨機查詢儘量使用ArrayList,ArrayList優於LinkedList,LinkedList還要移動指  針,新增刪除的操作LinkedList優於ArrayList,ArrayList還要移動資料,不過這是理論性分析,事實未必如此,重要的是理解好2  者得資料結構,對症下藥。

  23. 儘量使用System.arraycopy ()代替通過來迴圈複製陣列

  System.arraycopy() 要比通過迴圈來複制陣列快的多

  24. 儘量快取經常使用的物件

  儘可能將經常使用的物件進行快取,可以使用陣列,或HashMap的容器來進行快取,但這種方式可能導致系統佔用過多的快取,效能下降,推薦可以使用一些第三方的開源工具,如EhCache,Oscache進行快取,他們基本都實現了FIFO/FLU等快取演算法。

  25. 儘量避免非常大的記憶體分配

  有時候問題不是由當時的堆狀態造成的,而是因為分配失敗造成的。分配的記憶體塊都必須是連續的,而隨著堆越來越滿,找到較大的連續塊越來越困難。

  26. 慎用異常

  當建立一個異常時,需要收集一個棧跟蹤(stack  track),這個棧跟蹤用於描述異常是在何處建立的。構建這些棧跟蹤時需要為執行時棧做一份快照,正是這一部分開銷很大。當需要建立一個  Exception 時,JVM  不得不說:先別動,我想就您現在的樣子存一份快照,所以暫時停止入棧和出棧操作。棧跟蹤不只包含執行時棧中的一兩個元素,而是包含這個棧中的每一個元素。

  如 果您建立一個 Exception ,就得付出代價。好在捕獲異常開銷不大,因此可以使用 try-catch  將核心內容包起來。從技術上講,您甚至可以隨意地丟擲異常,而不用花費很大的代價。招致效能損失的並不是 throw  操作--儘管在沒有預先建立異常的情況下就丟擲異常是有點不尋常。真正要花代價的是建立異常。幸運的是,好的程式設計習慣已教會我們,不應該不管三七二十一就  丟擲異常。異常是為異常的情況而設計的,使用時也應該牢記這一原則。

  (1)。 用Boolean.valueOf(boolean b)代替new Boolean()

  包裝類的記憶體佔用是很恐怖的,它是基本型別記憶體佔用的N倍(N>2),同時new一個物件也是效能的消耗。

  我們再看看JDK對於Boolean.valueOf(boolean b)的實現:

  Boolean類提供了兩個常量:

 public static final Boolean TRUE = new Boolean(true);

  public static final Boolean FALSE = new Boolean(false);

  而valueOf(boolean b)的內部實現是:

  Java程式碼

  return (b ? TRUE : FALSE);

  因此用Boolean.valueOf(boolean b)代替new Boolean()既能節省空間,又能提高效能。

  (2)。 用Integer.valueOf(int i)代替new Integer()

  和Boolean類似,java開發中使用Integer封裝int的場合也非常多,並且通常用int表示的數值都非常小。SUN  SDK中對Integer的例項化進行了優化,Integer類快取了-128到127這256個狀態的Integer,如果使用  Integer.valueOf(int  i),傳入的int範圍正好在此內,就返回靜態例項。這樣如果我們使用Integer.valueOf代替new  Integer的話也將大大降低記憶體的佔用。

  (3)。 用StringBuffer的append方法代替"+"進行字串相加。

  這個已經被N多人說過N次了,這個就不多說了。

  (4)。 避免過深的類層次結構和過深的方法呼叫。

  因為這兩者都是非常佔用記憶體的(特別是方法呼叫更是堆疊空間的消耗大戶)。

  (5)。 變數只有在用到它的時候才定義和例項化。

  這是初學者最容易犯的錯,合理的使用變數,並且只有在用到它的時候才定義和例項化,能有效的避免記憶體空間和執行效能上的浪費,從而提高了程式碼的效率。

  (6)。 避免在迴圈體中宣告建立物件,即使該物件佔用記憶體空間不大。

  這種情況在我們的實際應用中經常遇到,而且我們很容易犯類似的錯誤,例如下面的程式碼:

  Java程式碼

  

for (int i = 0; i < 10000; ++i) {

  Object obj = new Object();

  System.out.println("obj= " + obj);

  }

  上面的做法會浪費較大的記憶體空間。正確的做法如下所示:

  Java程式碼

 

 Object obj = null;

  for (int i = 0; i < 10000; ++i) {

  obj = new Object();

  System.out.println("obj= "+ obj);

  }

  採用上面的第二種編寫方式,僅在記憶體中儲存一份對該物件的引用,而不像上面的第一種編寫方式中程式碼會在記憶體中產生大量的物件引用,浪費大量的記憶體空間,而且增大了垃圾回收的負荷。因此在迴圈體中宣告建立物件的編寫方式應該儘量避免。

  (7)。 如果if判斷中多個條件用'||'或者'&&'連線,請將出現頻率最高的條件放在表示式最前面。

  這個小技巧往往能有效的提高程式的效能,尤其是當if判斷放在迴圈體裡面時,效果更明顯。

  1.JVM管理兩種型別的記憶體:堆記憶體(heap),棧記憶體(stack),堆內在主要用來儲存程式在執行時建立或例項化的物件與變數。而棧記憶體則是用來儲存程式程式碼中宣告為靜態(static)(或非靜態)的方法。

  2.JVM中物件的生命週期,建立階段,應用階段,不可視階段,不可到達階段,可收集階段,終結階段,釋放階段

  3.避免在迴圈體中建立物件,即使該物件點用記憶體空間不大。

  

for(int i=0;i<10000;++i){

  Object obj = new Object();

  System.out.println("obj="+obj);

  }

  應改成

  

Object obj = null;

  for(int i=0;i<10000;++i){

  obj = new Object();

  System.out.println("obj="+obj);

  }

  4.軟引用的主要特點是具有較強的引用功能。只有當記憶體不夠的時候,才回收這類記憶體,因此在記憶體足夠的時候,它們通常不被回收。它可以用於實現一些常用資源的快取,實現Cache的功能

 

 A a = new A();

  SoftReference sr = new SoftReference(a);

  a = null;

  if(sr !=null){

  a = sr.get();

  }else{

  a = new A();

  sr = new SoftReference(a);

  }

  5.弱引用物件與Soft引用物件最大不同就在於:GC在進行回收時,需要通過演算法檢查是否回收Soft引用物件,而對於Weak引用物件,GC總是進行回收。

 

 A a = new A();

  WeakReference wr = new WeakReference(a);

  a = null;

  if(sr !=null){

  a = wr.get();

  }else{

  a = new A();

  wr = new WeakReference(a);

  }

  6.共享靜態變數儲存空間

  7.有時候我們為了提高系統效能,避免重複耗時的操作,希望能夠重用一些建立完成的物件,利用物件池實現。類似JDBC連線池。

  8.瞬間值,序列化物件大變數時,如果此大變數又沒有用途,則使用transient宣告,不序列化此變數。同時網路傳輸中也不傳輸。

  9.不要提前建立物件

  

void f(){

  int i;

  A a = new A();

  if(…){

  a.showMessage();

  }

  }

  改成

  

void f(){

  int i;

  A a = null;

  if(…){

  //用到時才例項化

  a = new A();

  a.showMessage();

  }

  }

  10 .(1)最基本的建議就是儘早釋放無用物件的引用

  A a = new A();

  a = null; //當使用物件a之後主動將其設定為空

  (2)儘量少用finalize函式。

  (3) 如果需要使用經常用到的圖片展,可以使用軟引用。

  (4) 注意集合資料型別,包括陣列,樹等資料,這些資料結構對GC來說,回收更為複雜,

  (5) 儘量避免在類的預設構造器中建立,初始化大量的物件,防止在呼叫其自類的構造器時造成不必要的記憶體資源浪費。

  (6) 儘量避免強制系統做垃圾記憶體回收。

  (7) 儘量避免顯式申請陣列空間。

  (8) 儘量在合適的場景下使用物件池技術以提高系統效能,縮減系統記憶體開銷。

  11.當做陣列拷貝操作時,採用System.arraycopy()方法完成拷貝操作要比採用迴圈的辦法完成陣列拷貝操作效率高

  12. 儘量避免在迴圈體中呼叫方法,因為方法呼叫是比較昂貴的。

  13. 儘量避免在迴圈體中使用try-catch 塊,最好在迴圈體外使用try--catch塊以提高系統效能。

  14. 在多重迴圈中,如果有可能,儘量將最長的迴圈放在最內層,最短的迴圈放在最外層,以減少迴圈層間的變換次數。

  15. 在需要執行緒安全的情況下,使用List list = Collections.synchronizedList(new ArrayList());

  16. 如果預知長度,就設定ArrayList的長度。

  17. ArrayList 與 LinkedList 選擇,熟悉底層的實現原理,選擇適當的容器。

  18. 字串累加採用StringBuffer.

  19. 系統I/O優化,採用緩衝和壓縮技術。優化效能。

  20. 避免在類在構造器的初始化其他類

  21 儘量避免在構造中對靜態變數做賦值操作

  22. 不要在類的構造器中建立類的例項

  23. 組合優化繼承

  24. 最好通過Class.forname() 動態的裝載類

  25. JSP優化,採用out 物件中的print方法代替println()方法

  26 .採用ServletOutputStream 物件代替JSPWriter物件

  27. 採用適當的值初始化out 物件緩衝區的大小

  28. 儘量採用forward()方法重定向新的JSP

  29. 利用執行緒池技術處理客戶請求

  30.Servlet優化

  (1) 通過init()方法來快取一些靜態資料以提高應用效能。

  (2) 用print() 方法取代println()方法。

  (3) 用ServletOutputStream 取代 PrintWriter.

  (4) 儘量縮小同步程式碼數量

  31. 改善Servlet應用效能的方法

  (1)不要使用SingleThreadModel

  (2)使用執行緒池ThreadPool

  32. EJB優化

  實體EJB:

  (1)實體EJB中常用資料快取與釋放

  (2)採用延遲載入的方式裝載關聯資料

  (3)儘可能地應用CMP型別實體EJB

  (4)直接採用JDBC技術處理大型資料

  33. 優化JDBC連線

  (1)設定合適的預取行值

  (2)採用連線池技術

  (3)全合理應用事務

  (4)選擇合適的事務隔離層與及時關閉連線物件

  34. PreparedStatemetn只編譯解析一次,而Statement每次都編譯解析。

  35. 儘可能地做批處理更新

  36. 通過採用合適的getXXX方法提高系統效能

  37. 採用設計模式。

相關文章