<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java 性能優(yōu)化的 50 個細節(jié)(珍藏版)

          共 11651字,需瀏覽 24分鐘

           ·

          2021-08-18 20:51

          在JAVA程序中,性能問題的大部分原因并不在于JAVA語言,而是程序本身。養(yǎng)成良好的編碼習(xí)慣非常重要,能夠顯著地提升程序性能。


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

          使用單例可以減輕加載的負擔(dān),縮短加載的時間,提高加載的效率,但并不是所有地方都適用于單例


          簡單來說,單例主要適用于以下三個方面:


          1. 控制資源的使用,通過線程同步來控制資源的并發(fā)訪問;

          2. 控制實例的產(chǎn)生,以達到節(jié)約資源的目的;

          3. 控制數(shù)據(jù)共享,在不建立直接關(guān)聯(lián)的條件下,讓多個不相關(guān)的進程或線程之間實現(xiàn)通信。


          2. 盡量避免隨意使用靜態(tài)變量

          當(dāng)某個對象被定義為static變量所引用,那么GC通常是不會回收這個對象所占有的內(nèi)存,如:

          public class A{
            private static B b = new B();
          }

          此時靜態(tài)變量 b 的生命周期與A類同步,如果A類不會卸載,那么b對象會常駐內(nèi)存,直到程序終止。


          3. 盡量避免過多過常地創(chuàng)建Java對象

          盡量避免在經(jīng)常調(diào)用的方法,循環(huán)中new對象,由于系統(tǒng)不僅要花費時間來創(chuàng)建對象,而且還要花時間對這些對象進行垃圾回收和處理


          在我們可以控制的范圍內(nèi),最大限度地重用對象,最好能用基本的數(shù)據(jù)類型或數(shù)組來替代對象。


          4. 盡量使用final修飾符

          帶有final修飾符的類是不可派生的。在JAVA核心API中,有許多應(yīng)用final的例子,例如java、lang、String,為String類指定final防止了使用者覆蓋length()方法。


          另外,如果一個類是final的,則該類所有方法都是final的。java編譯器會尋找機會內(nèi)聯(lián)(inline)所有的final方法(這和具體的編譯器實現(xiàn)有關(guān)),此舉能夠使性能平均提高50%。


          如:讓訪問實例內(nèi)變量的getter/setter方法變成”final:簡單的getter/setter方法應(yīng)該被置成final,這會告訴編譯器,這個方法不會被重載,所以,可以變成”inlined”,例子:

          class MAF {
            public void setSize (int size) {
              _size = size;
            }
            private int _size;
          }

          更正
          class DAF_fixed {
            final public void setSize (int size) {
              _size = size;
            }
            private int _size;
          }



          5. 盡量使用局部變量

          調(diào)用方法時傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時變量都保存在棧(Stack)中,速度較快;其他變量,如靜態(tài)變量、實例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。


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

          雖然包裝類型和基本類型在使用過程中是可以相互轉(zhuǎn)換,但它們兩者所產(chǎn)生的內(nèi)存區(qū)域是完全不同的


          基本類型數(shù)據(jù)產(chǎn)生和處理都在棧中處理,包裝類型是對象,是在堆中產(chǎn)生實例。在集合類對象,有對象方面需要的處理適用包裝類型,其他的處理提倡使用基本類型。


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

          都知道,實現(xiàn)同步是要很大的系統(tǒng)開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。


          synchronize方法被調(diào)用時,直接會把當(dāng)前對象鎖了,在方法執(zhí)行完之前其他線程無法調(diào)用當(dāng)前對象的其他方法。


          所以,synchronize的方法盡量減小,并且應(yīng)盡量使用方法同步代替代碼塊同步。


          9. 盡量不要使用finalize方法

          實際上,將資源清理放在finalize方法中完成是非常不好的選擇


          由于GC的工作量很大,尤其是回收Young代內(nèi)存時,大都會引起應(yīng)用程序暫停,所以再選擇使用finalize方法進行資源清理,會導(dǎo)致GC負擔(dān)更大,程序運行效率更差。


          10. 盡量使用基本數(shù)據(jù)類型代替對象


          String str = "hello";

          上面這種方式會創(chuàng)建一個“hello”字符串,而且JVM的字符緩存池還會緩存這個字符串;


          String str = new String("hello");

          此時程序除創(chuàng)建字符串外,str所引用的String對象底層還包含一個char[]數(shù)組,這個char[]數(shù)組依次存放了h,e,l,l,o


          11. 多線程在未發(fā)生線程安全前提下應(yīng)盡量使用HashMap、ArrayList

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


          12. 盡量合理的創(chuàng)建HashMap

          當(dāng)你要創(chuàng)建一個比較大的hashMap時,充分利用這個構(gòu)造函數(shù)


          public HashMap(int initialCapacity, float loadFactor);


          避免HashMap多次進行了hash重構(gòu),擴容是一件很耗費性能的事


          在默認中initialCapacity只有16,而loadFactor是 0.75,需要多大的容量,你最好能準(zhǔn)確的估計你所需要的最佳大小,同樣的Hashtable,Vectors也是一樣的道理。


          13. 盡量減少對變量的重復(fù)計算


          如:

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


          應(yīng)該改為:

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

          并且在循環(huán)中應(yīng)該避免使用復(fù)雜的表達式,在循環(huán)中,循環(huán)條件會被反復(fù)計算,如果不使用復(fù)雜表達式,而使循環(huán)條件值不變的話,程序?qū)\行的更快。


          14. 盡量避免不必要的創(chuàng)建

          如:

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


          應(yīng)該改為:

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


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

          程序中使用到的資源應(yīng)當(dāng)被釋放,以避免資源泄漏,這最好在finally塊中去做。不管程序執(zhí)行的結(jié)果如何,finally塊總是會執(zhí)行的,以確保資源的正確關(guān)閉。


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

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

          如:

          int num = a / 4;
          int num = a / 8;


          應(yīng)該改為:

          int num = a >> 2;
          int num = a >> 3;


          但注意的是使用移位應(yīng)添加注釋,因為移位操作不直觀,比較難理解。


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

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

          如:

          int num = a * 4;
          int num = a * 8;


          應(yīng)該改為:

          int num = a << 2;
          int num = a << 3;


          18. 盡量確定StringBuffer的容量

          StringBuffer 的構(gòu)造器會創(chuàng)建一個默認大小(通常是16)的字符數(shù)組。


          在使用中,如果超出這個大小,就會重新分配內(nèi)存,創(chuàng)建一個更大的數(shù)組,并將原先的數(shù)組復(fù)制過來,再丟棄舊的數(shù)組。


          在大多數(shù)情況下,你可以在創(chuàng)建 StringBuffer的時候指定大小,這樣就避免了在容量不夠的時候自動增長,以提高性能。如:

          StringBuffer buffer = new StringBuffer(1000);


          19. 盡量早釋放無用對象的引用

          大部分時,方法局部引用變量所引用的對象會隨著方法結(jié)束而變成垃圾,因此,大部分時候程序無需將局部,引用變量顯式設(shè)為null。例如:


          Java代碼

          Public void test(){
            Object obj = new Object();
            ……
            Obj = null;
          }


          上面這個就沒必要了,隨著方法test()的執(zhí)行完成,程序中obj引用變量的作用域就結(jié)束了。


          但是如果是改成下面:


          Java代碼

          Public void test(){
            Object obj = new Object();
            ……
            Obj = null;
            //執(zhí)行耗時,耗內(nèi)存操作;或調(diào)用耗時,耗內(nèi)存的方法
            ……
          }


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


          20. 盡量避免使用二維數(shù)組

          二維數(shù)據(jù)占用的內(nèi)存空間比一維數(shù)組多得多,大概10倍以上。


          21. 盡量避免使用split

          除非是必須的,否則應(yīng)該避免使用split,split由于支持正則表達式,所以效率比較低


          如果是頻繁的幾十,幾百萬的調(diào)用將會耗費大量資源,如果確實需要頻繁的調(diào)用split,可以考慮使用apache的StringUtils.split(string,char),頻繁split的可以緩存結(jié)果


          22. ArrayList & LinkedList

          一個是線性表,一個是鏈表,一句話,隨機查詢盡量使用ArrayList,ArrayList優(yōu)于LinkedList,LinkedList還要移動指針,添加刪除的操作LinkedList優(yōu)于ArrayList,ArrayList還要移動數(shù)據(jù)


          不過這是理論性分析,事實未必如此,重要的是理解好2者得數(shù)據(jù)結(jié)構(gòu),對癥下藥。


          23. 盡量使用System.arraycopy ()代替通過來循環(huán)復(fù)制數(shù)組

          System.arraycopy() 要比通過循環(huán)來復(fù)制數(shù)組快的多。


          24. 盡量緩存經(jīng)常使用的對象

          盡可能將經(jīng)常使用的對象進行緩存,可以使用數(shù)組,或HashMap的容器來進行緩存,但這種方式可能導(dǎo)致系統(tǒng)占用過多的緩存,性能下降


          推薦可以使用一些第三方的開源工具,如EhCache,Oscache進行緩存,他們基本都實現(xiàn)了FIFO/FLU等緩存算法。


          25. 盡量避免非常大的內(nèi)存分配

          有時候問題不是由當(dāng)時的堆狀態(tài)造成的,而是因為分配失敗造成的。分配的內(nèi)存塊都必須是連續(xù)的,而隨著堆越來越滿,找到較大的連續(xù)塊越來越困難。


          26. 慎用異常

          當(dāng)創(chuàng)建一個異常時,需要收集一個棧跟蹤(stack track),這個棧跟蹤用于描述異常是在何處創(chuàng)建的。


          構(gòu)建這些棧跟蹤時需要為運行時棧做一份快照,正是這一部分開銷很大。


          當(dāng)需要創(chuàng)建一個 Exception 時,JVM 不得不說:先別動,我想就您現(xiàn)在的樣子存一份快照,所以暫時停止入棧和出棧操作。棧跟蹤不只包含運行時棧中的一兩個元素,而是包含這個棧中的每一個元素。


          如果您創(chuàng)建一個 Exception ,就得付出代價,好在捕獲異常開銷不大,因此可以使用 try-catch 將核心內(nèi)容包起來。


          從技術(shù)上講,你甚至可以隨意地拋出異常,而不用花費很大的代價。招致性能損失的并不是 throw 操作——盡管在沒有預(yù)先創(chuàng)建異常的情況下就拋出異常是有點不尋常。


          真正要花代價的是創(chuàng)建異常,幸運的是,好的編程習(xí)慣已教會我們,不應(yīng)該不管三七二十一就拋出異常。異常是為異常的情況而設(shè)計的,使用時也應(yīng)該牢記這一原則。


          27. 盡量重用對象

          特別是String對象的使用中,出現(xiàn)字符串連接情況時應(yīng)使用StringBuffer代替,由于系統(tǒng)不僅要花時間生成對象,以后可能還需要花時間對這些對象進行垃圾回收和處理。因此生成過多的對象將會給程序的性能帶來很大的影響。


          28. 不要重復(fù)初始化變量

          默認情況下,調(diào)用類的構(gòu)造函數(shù)時,java會把變量初始化成確定的值,所有的對象被設(shè)置成null,整數(shù)變量設(shè)置成0,float和double變量設(shè)置成0.0,邏輯值設(shè)置成false。


          當(dāng)一個類從另一個類派生時,這一點尤其應(yīng)該注意,因為用new關(guān)鍵字創(chuàng)建一個對象時,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。


          這里有個注意,給成員變量設(shè)置初始值但需要調(diào)用其他方法的時候,最好放在一個方法。比如initXXX()中,因為直接調(diào)用某方法賦值可能會因為類尚未初始化而拋空指針異常,如:public int state = this.getState()。


          29. 在java+Oracle的應(yīng)用系統(tǒng)開發(fā)中,java中內(nèi)嵌的SQL語言應(yīng)盡量使用大寫形式,以減少Oracle解析器的解析負擔(dān)。


          30. 在java編程過程中,進行數(shù)據(jù)庫連接,I/O流操作,在使用完畢后,及時關(guān)閉以釋放資源。因為對這些大對象的操作會造成系統(tǒng)大的開銷。


          31. 過分的創(chuàng)建對象會消耗系統(tǒng)的大量內(nèi)存,嚴重時,會導(dǎo)致內(nèi)存泄漏


          因此,保證過期的對象的及時回收具有重要意義。JVM的GC并非十分智能,因此建議在對象使用完畢后,手動設(shè)置成null。


          32. 在使用同步機制時,應(yīng)盡量使用方法同步代替代碼塊同步。


          33. 不要在循環(huán)中使用Try/Catch語句,應(yīng)把Try/Catch放在循環(huán)最外層

          Error是獲取系統(tǒng)錯誤的類,或者說是虛擬機錯誤的類。不是所有的錯誤Exception都能獲取到的,虛擬機報錯Exception就獲取不到,必須用Error獲取。


          34. 通過StringBuffer的構(gòu)造函數(shù)來設(shè)定它的初始化容量,可以明顯提升性能

          StringBuffer的默認容量為16,當(dāng)StringBuffer的容量達到最大容量時,它會將自身容量增加到當(dāng)前的2倍+2,也就是2*n+2。


          無論何時,只要StringBuffer到達它的最大容量,它就不得不創(chuàng)建一個新的對象數(shù)組,然后復(fù)制舊的對象數(shù)組,這會浪費很多時間。


          所以給StringBuffer設(shè)置一個合理的初始化容量值,是很有必要的!


          35. 合理使用java.util.Vector

          Vector與StringBuffer類似,每次擴展容量時,所有現(xiàn)有元素都要賦值到新的存儲空間中。


          Vector的默認存儲能力為10個元素,擴容加倍


          vector.add(index,obj) 這個方法可以將元素obj插入到index位置,但index以及之后的元素依次都要向下移動一個位置(將其索引加 1)。除非必要,否則對性能不利。


          同樣規(guī)則適用于remove(int index)方法,移除此向量中指定位置的元素。將所有后續(xù)元素左移(將其索引減 1)。返回此向量中移除的元素。


          所以刪除vector最后一個元素要比刪除第1個元素開銷低很多。刪除所有元素最好用removeAllElements()方法。


          如果要刪除vector里的一個元素可以使用 vector.remove(obj);而不必自己檢索元素位置,再刪除,如int index = indexOf(obj);vector.remove(index)。


          38. 不用new關(guān)鍵字創(chuàng)建對象的實例

          用new關(guān)鍵詞創(chuàng)建類的實例時,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。


          但如果一個對象實現(xiàn)了Cloneable接口,我們可以調(diào)用它的clone()方法。clone()方法不會調(diào)用任何類構(gòu)造函數(shù)。


          下面是Factory模式的一個典型實現(xiàn):

          public static Credit getNewCredit() {
            return new Credit();
          }


          改進后的代碼使用clone()方法:

          private static Credit BaseCredit = new Credit();
          public static Credit getNewCredit() {
            return (Credit)BaseCredit.clone();
          }


          39. 不要將數(shù)組聲明為:public static final


          40. HaspMap的遍歷

          Map<StringString[]> paraMap = new HashMap<StringString[]>();

          for
          (Entry<StringString[]> entry : paraMap.entrySet()) {
            String appFieldDefId = entry.getKey();
            String[] values = entry.getValue();
          }


          利用散列值取出相應(yīng)的Entry做比較得到結(jié)果,取得entry的值之后直接取key和value。


          41. array(數(shù)組)和ArrayList的使用

          array 數(shù)組效率最高,但容量固定,無法動態(tài)改變,ArrayList容量可以動態(tài)增長,但犧牲了效率。


          42. 單線程應(yīng)盡量使用 HashMap, ArrayList,除非必要,否則不推薦使用HashTable,Vector,它們使用了同步機制,而降低了性能。


          43. StringBuffer,StringBuilder的區(qū)別在于:java.lang.StringBuffer 線程安全的可變字符序列。一個類似于String的字符串緩沖區(qū),但不能修改。


          StringBuilder與該類相比,通常應(yīng)該優(yōu)先使用StringBuilder類,因為它支持所有相同的操作,但由于它不執(zhí)行同步,所以速度更快。


          為了獲得更好的性能,在構(gòu)造StringBuffer或StringBuilder時應(yīng)盡量指定她的容量。當(dāng)然如果不超過16個字符時就不用了。


          相同情況下,使用StringBuilder比使用StringBuffer僅能獲得10%~15%的性能提升,但卻要冒多線程不安全的風(fēng)險。綜合考慮還是建議使用StringBuffer。


          44. 盡量使用基本數(shù)據(jù)類型代替對象。


          45. 使用具體類比使用接口效率高,但結(jié)構(gòu)彈性降低了,但現(xiàn)代IDE都可以解決這個問題。


          46. 考慮使用靜態(tài)方法,如果你沒有必要去訪問對象的外部,那么就使你的方法成為靜態(tài)方法。它會被更快地調(diào)用,因為它不需要一個虛擬函數(shù)導(dǎo)向表。


          這同時也是一個很好的實踐,因為它告訴你如何區(qū)分方法的性質(zhì),調(diào)用這個方法不會改變對象的狀態(tài)。


          47. 應(yīng)盡可能避免使用內(nèi)在的GET,SET方法。


          48.避免枚舉,浮點數(shù)的使用。



          以下舉幾個實用優(yōu)化的例子:

          一、避免在循環(huán)條件中使用復(fù)雜表達式

          在不做編譯優(yōu)化的情況下,在循環(huán)中,循環(huán)條件會被反復(fù)計算,如果不使用復(fù)雜表達式,而使循環(huán)條件值不變的話,程序?qū)\行的更快。


          例子:

          import java.util.Vector;
          class CEL {
            void method (Vector vector) {
            for (int i = 0; i < vector.size (); i++) // Violation
              // ...
            }
          }


          更正:

          class CEL_fixed {
            void method (Vector vector) {
            int size = vector.size ();
            for (int i = 0; i < size; i++)
              // ...
            }
          }


          二、為'Vectors' 和 'Hashtables'定義初始大小

          JVM為Vector擴充大小的時候需要重新創(chuàng)建一個更大的數(shù)組,將原原先數(shù)組中的內(nèi)容復(fù)制過來,最后,原先的數(shù)組再被回收。可見Vector容量的擴大是一個頗費時間的事。


          通常,默認的10個元素大小是不夠的。你最好能準(zhǔn)確的估計你所需要的最佳大小。例子:

          import java.util.Vector;
          public class DIC {
            public void addObjects (Object[] o) {
              // if length > 10, Vector needs to expand
              for (int i = 0; i< o.length;i++) {
                v.add(o); // capacity before it can add more elements.
              }
            }
            public Vector v = new Vector(); // no initialCapacity.
          }


          更正:

          自己設(shè)定初始大小。

          public Vector v = new Vector(20);
          public Hashtable hash = new Hashtable(10);


          三、在finally塊中關(guān)閉Stream

          程序中使用到的資源應(yīng)當(dāng)被釋放,以避免資源泄漏。這最好在finally塊中去做。不管程序執(zhí)行的結(jié)果如何,finally塊總是會執(zhí)行的,以確保資源的正確關(guān)閉。


          四、使用'System.arraycopy ()'代替通過來循環(huán)復(fù)制數(shù)組

          例子:

          public class IRB {
            void method () {
              int[] array1 = new int [100];
              for (int i = 0; i < array1.length; i++) {
                array1 [i] = i;
              }
              int[] array2 = new int [100];
              for (int i = 0; i < array2.length; i++) {
                array2 [i] = array1 [i]; // Violation
              }
            }
          }


          更正:

          public class IRB {
            void method () {
              int[] array1 = new int [100];
              for (int i = 0; i < array1.length; i++) {
                array1 [i] = i;
              }
              int[] array2 = new int [100];
              System.arraycopy(array1, 0, array2, 0100);
            }
          }


          五、讓訪問實例內(nèi)變量的getter/setter方法變成”final”

          簡單的getter/setter方法應(yīng)該被置成final,這會告訴編譯器,這個方法不會被重載,所以,可以變成”inlined”,例子:


          class MAF {
            public void setSize (int size) {
              _size = size;
            }
            private int _size;
          }


          更正:

          class DAF_fixed {
            final public void setSize (int size) {
              _size = size;
            }
            private int _size;
          }


          六、對于常量字符串,用'String' 代替 'StringBuffer'

          常量字符串并不需要動態(tài)改變長度。


          例子:

          public class USC {
            String method () {
              StringBuffer s = new StringBuffer ("Hello");
              String t = s + "World!";
              return t;
            }
          }


          更正:把StringBuffer換成String,如果確定這個String不會再變的話,這將會減少運行開銷提高性能。


          七、在字符串相加的時候,使用 ' ' 代替 " ",如果該字符串只有一個字符的話


          例子:

          public class STR {
            public void method(String s) {
              String string = s + "d" // violation.
              string = "abc" + "d" // violation.
            }
          }


          更正:

          將一個字符的字符串替換成' '

          public class STR {
            public void method(String s) {
              String string = s + 'd'
              string = "abc" + 'd'
            }
          }


          以上僅是Java方面編程時的性能優(yōu)化,性能優(yōu)化大部分都是在時間、效率、代碼結(jié)構(gòu)層次等方面的權(quán)衡,各有利弊


          不要把上面內(nèi)容當(dāng)成教條,或許有些對我們實際工作適用,有些不適用,還望根據(jù)實際工作場景進行取舍,活學(xué)活用,變通為宜。



          作者:老夏

          來源:21CTO

          推薦閱讀:

          世界的真實格局分析,地球人類社會底層運行原理

          不是你需要中臺,而是一名合格的架構(gòu)師(附各大廠中臺建設(shè)PPT)

          企業(yè)IT技術(shù)架構(gòu)規(guī)劃方案

          論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?

          華為干部與人才發(fā)展手冊(附PPT)

          企業(yè)10大管理流程圖,數(shù)字化轉(zhuǎn)型從業(yè)者必備!

          【中臺實踐】華為大數(shù)據(jù)中臺架構(gòu)分享.pdf

          華為的數(shù)字化轉(zhuǎn)型方法論

          華為如何實施數(shù)字化轉(zhuǎn)型(附PPT)

          超詳細280頁Docker實戰(zhàn)文檔!開放下載

          華為大數(shù)據(jù)解決方案(PPT)

          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产在视频一区二区三区吞精 | 中文字幕北条麻妃 | 中文在线A∨在线 | 国产精品123区在线观看 | 日韩免费黄色 |