Java效能調優的11個實用技巧
大多數開發人員認為效能優化是個比較複雜的問題,需要大量的經驗和知識。是的,這並不沒有錯。誠然,優化應用程式以獲得最好的效能並不是一件容易的事情,但這並不意味著你在沒有獲得這些經驗和知識之前就不能做任何事。下面有幾個很容易遵循的建議和最佳實踐能夠幫你建立一個性能良好的應用程式。
這些建議中的大多數都是基於Java的,但是也不一定,也有一些是可以應用於所有的應用程式和程式語言的。在我們分享基於Java的效能調優技巧之前,讓我們先討論一下這些通用的效能調優技巧。
1.在必要之前,先不要優化
這可能是最最重要的效能調優技巧之一。你應該遵循常見的最佳實踐,並嘗試有效地實現你的用例。但這並不意味著在證明它是必要之前,替換任何標準庫或構建複雜的優化。
在大多數情況下,過早的優化佔用了大量的時間,使得程式碼難以讀取和維護。更糟糕的是,這些優化通常不會帶來任何好處,因為你花費了大量時間來優化應用程式的非關鍵部分。
那麼,你如何證明你需要優化某些東西呢?
首先,你需要確定應用程式程式碼的速度,例如,為所有API呼叫指定一個最大響應時間,或者指定在特定時間範圍內匯入的記錄數量。完成之後,你可以度量應用程式的哪些部分太慢而需要改進。當這樣做之後,那麼請繼續看第二個調優技巧。
2.使用分析器來找到真正的瓶頸
在你遵循第一條建議,並確定你的應用程式的某些部分的確需要改進之後,問自己從哪裡開始?
你可以用兩種方法來解決這個問題:
- 你可以看一下你的程式碼,從看起來可疑或者你覺得它可能會產生問題的部分開始。
- 或者使用分析器,獲取程式碼中每個部分的行為和效能的詳細資訊。
至於為什麼應該總是遵循第二種方法。
答案應該很明顯,基於分析器的方法能讓你更好地理解程式碼的效能含義,並允許你關注最關鍵的部分。如果你曾經使用過分析器,你將會驚訝於程式碼的哪些部分造成了效能問題。然而,很多時候,你的第一次猜想會把你引向錯誤的方向。
3 .為整個應用程式建立效能測試套件
這是另一個幫助你避免許多意想不到問題的一般技巧,這些問題通常發生在效能改進部署到生產環境之後。你應該經常定義測試整個應用程式的效能測試套件,並在你完成效能改進之前和之後執行它。
這些額外的測試執行將幫助你識別更改的功能和效能方面的影響,並確保你不會發佈一個弊大於利的更新。如果你的任務運行於應用程式的多個不同部分比如資料庫或快取,這一點尤其重要。
4.首先解決最大的瓶頸問題
在建立了測試套件並使用分析器對應用程式進行分析之後,你就有了一個需要提高效能的問題列表,這很好,但它仍然不能回答你應該從哪裡開始的問題。你可以從那些可以快速搞定的開始,亦或者從最重要的問題開始。
當然前者很誘人,因為這很快就能出結果。有時,可能需要說服其他團隊成員或你的管理層,效能分析是值得的。
但總的來說,我建議首先著手處理最重要的效能問題。這將為你提供最大的效能改進,而且你可能只需要修復這些問題中的幾個就可以解決你的效能需求。
在瞭解通用效能調優技巧之後,讓我們再來仔細看看一些特定於Java的調優技巧。
5.使用StringBuilder以程式設計方式連線字串
在Java中有許多不同的連線字串的選項。例如,可以使用一個簡單的+或+ =、老的StringBuffer或StringBuilder。
那麼,你應該選擇哪種方法呢?
答案取決於連線字串的程式碼。如果你以程式設計方式向字串中新增新內容,例如,在for迴圈中,你應該使用StringBuilder。它比StringBuffer更容易使用和提供更好的效能。但是請記住,StringBuilder與StringBuffer不同,它不是執行緒安全的,而且可能不適合所有用例。
你只需要例項化一個新的StringBuilder,並呼叫append方法在字串中新增一個新的部分。當你添加了所有的部分後,可以呼叫toString()方法來檢索連線字串。
下面的程式碼片段展示了一個簡單的示例。在每次迭代過程中,這個迴圈將i轉換成一個字串,並將其新增到StringBuilder sb的空間中,因此到最後,這段程式碼寫入“this is test0123456789”到日誌檔案。
程式碼
-
StringBuilder sb = new StringBuilder(“This is a test”); for (int i=0; i<10; i++) { sb.append(i); sb.append(” “); } log.info(sb.toString());
正如在程式碼片段中看到的,你可以為建構函式方法提供字串的第一個元素。這將建立一個新的StringBuilder,其中包含提供的字串和16個額外字元的容量。當你向StringBuilder中新增更多字元時,JVM將動態地改變StringBuilder的大小。
如果你已經知道自己的字串包含多少字元,那麼你可以向不同的建構函式方法提供這個數字,以例項化一個具有被定義容量的StringBuilder。這進一步提高了它的效率,因為它不需要動態擴充套件它的容量。
6.在宣告中使用+連線字串
當你在Java中實現第一個應用程式時,可能有人告訴你不應該用+來連線字串。如果在應用程式邏輯中連線字串這是正確的。字串是不可變的,每個字串連線的結果儲存在一個新的字串物件中。這需要額外的記憶體,並降低應用程式的速度,特別是在迴圈中連線多個字串時。
在這些情況下,你應該遵循tip 5並使用StringBuilder。
但如果你只是將一個字串分解成多行來提高程式碼的可讀性,那就不是這樣了。
程式碼
-
Query q = em.createQuery(“SELECT a.id, a.firstName, a.lastName ” + “FROM Author a ” + “WHERE a.id = :id”);
在這些情況下,你應該用一個簡單的+來連線你的字串。Java編譯器將優化它並在編譯時執行連線。因此,在執行時,程式碼只使用1個字元,不需要連線。
7.儘可能使用基本資料型別
另一種避免開銷,提高應用程式效能的快速方法就是使用原始資料型別而不是它們的包裝類。因此,最好是使用int而不是Integer,或者是double而不是Double。這將讓JVM將值儲存在堆疊中,以減少記憶體消耗,並更有效地處理它。
8.儘量避免BigInteger和BigDecimal
由於我們已經討論了資料型別,我們再來看下BigInteger和BigDecimal。尤其是後者,由於其精度高而受歡迎。但這是有代價的。
BigInteger和BigDecimal比簡單的long或double需要更多的記憶體,並且大大降低所有的計算速度。因此,如果你需要額外的精度,或者你的數字超過了一個long範圍,最好三思而後行。這可能是你在提升效能問題中唯一需要更改的地方,特別是當你正在實現一個數學演算法。
9.首先檢查當前日誌級別
這個建議是顯而易見的,但不幸的是,你會發現許多程式碼忽略它。在建立除錯訊息之前,應該先檢查當前日誌級別。
這裡有兩個例子來說明你不應該這樣做。
程式碼
-
// don’t do this log.debug(“User [” + userName + “] called method X with [” + i + “]”); // or this log.debug(String.format(“User [%s] called method X with [%d]”, userName, i));
在這兩種情況下,你將執行所有需要的步驟來建立日誌訊息,而不知道日誌框架是否使用日誌訊息。在建立除錯訊息之前,最好先檢查當前日誌級別。
程式碼
-
// do this if (log.isDebugEnabled()) { log.debug(“User [” + userName + “] called method X with [” + i + “]”); }
10.使用Apache Commons StringUtils.Replace 代替String.replace
一般來說,String.replace 方法工作得很好,而且非常高效,特別是如果你使用的是Java 9。但是,如果應用程式需要大量的替換操作,並且你還沒有更新到最新的Java版本,那麼檢查更快和更有效的替代方案仍然是有意義的。
一個候選就是 Apache Commons Lang’s StringUtils.replace 方法。正如Lukas Eder在他最近的一篇部落格文章中所描述的那樣,它大大超過了Java 8的String.replace 方法。
它只需要很小的改變。你只需要為Apache’s Commons Lang 專案增加一個Maven依賴項到你的應用pom.xml,並用StringUtils.replace方法替換所有String.replace方法的呼叫。
程式碼
-
// replace this test.replace(“test”, “simple test”); // with this StringUtils.replace(test, “test”, “simple test”);
11.快取昂貴的資源,比如資料庫連線
快取是一種流行的解決方案來避免重複執行昂貴或頻繁使用的程式碼片段。一般的想法很簡單:重複使用這些資源比一次又一次地建立一個新的資源要便宜得多。
一個典型的例子就是在池中快取資料庫連線。建立新連線需要時間,如果重用現有連線,則可以避免。
還可以在Java語言本身中找到其他示例。例如,Integer類的valueOf方法快取了- 128和127之間的值。你可能會說,建立一個新整數並不太貴,但它經常使用,快取最常用的值提供了效能方面的好處。
但當你考慮快取時,請記住,快取實現也會產生開銷。你需要花費額外的記憶體來儲存可重用資源,因此可能需要管理你的快取,以使資源能夠訪問或刪除過時的資源。
因此,在你開始快取任何資源之前,請確保是經常使用。
總結
正如你所看到的,提高應用程式的效能有時不需要做大量的工作。這篇文章中的大多數建議,其實只需要稍微的努力就可以將它們應用到程式碼中。
但通常最重要的建議是很程式語言無關的:
-
- 在你知道有必要之前,不要優化
- 使用分析器來找到真正的瓶頸
- 首先解決最大的瓶頸問題