關於Java性能調優你所不知道的地方

[11 個簡單的 Java 性能調優技巧]

大多數開發人員理所當然地以爲性能優化很複雜,需要大量的經驗和知識。好吧,不能說這是完全錯誤的。優化應用程序以獲得最佳性能不是一件容易的事情。但是,這並不意味着如果你不具備這些知識,就不能做任何事情。這裏有11個易於遵循的建議和最佳實踐可以幫助你創建一個性能良好的應用程序。

大部分建議是針對Java的。但也有若干建議是與語言無關的,可以應用於所有應用程序和編程語言。在討論專門針對Java的性能調優技巧之前,讓我們先來看看通用技巧。

1.在你知道必要之前不要優化

這可能是最重要的性能調整技巧之一。你應該遵循常見的最佳實踐做法並嘗試高效地實現用例。但是,這並不意味着在你證明必要之前,你應該更換任何標準庫或構建複雜的優化。

在大多數情況下,過早優化不但會佔用大量時間,而且會使代碼變得難以閱讀和維護。更糟糕的是,這些優化通常不會帶來任何好處,因爲你花費大量時間來優化的是應用程序的非關鍵部分。

那麼,你如何證明你需要優化一些東西呢?

首先,你需要定義應用程序代碼的速度得多快,例如,爲所有API調用指定最大響應時間,或者指定在特定時間範圍內要導入的記錄數量。在完成這些之後,你就可以測量應用程序的哪些部分太慢需要改進。然後,接着看第二個技巧。

2.使用分析器查找真正的瓶頸

在你遵循第一個建議並確定了應用程序的某些部分需要改進後,那麼從哪裏開始呢?

你可以用兩種方法來解決問題:

  • 查看你的代碼,並從看起來可疑或者你覺得可能會產生問題的部分開始。
  • 或者使用分析器並獲取有關代碼每個部分的行爲和性能的詳細信息。

希望不需要我解釋爲什麼應該始終遵循第二種方法的原因。

很明顯,基於分析器的方法可以讓你更好地理解代碼的性能影響,並使你能夠專注於最關鍵的部分。如果你曾使用過分析器,那麼你一定記得曾經你是多麼驚訝於一下就找到了代碼的哪些部分產生了性能問題。老實說,我第一次的猜測不止一次地導致我走錯了方向。

3.爲整個應用程序創建性能測試套件

這是另一個通用技巧,可以幫助你避免在將性能改進部署到生產後經常會發生的許多意外問題。你應該總是定義一個測試整個應用程序的性能測試套件,並在性能改進之前和之後運行它。

這些額外的測試運行將幫助你識別更改的功能和性能副作用,並確保不會導致弊大於利的更新。如果你工作於被應用程序若干不同部分使用的組件,如數據庫或緩存,那麼這一點就尤其重要。

4.首先處理最大的瓶頸

在創建測試套件並使用分析器分析應用程序之後,你可以列出一系列需要解決以提高性能的問題。這很好,但它仍然不能回答你應該從哪裏開始的問題。你可以專注於速效方案,或從最重要的問題開始。

速效方案一開始可能會很有吸引力,因爲你可以很快顯示第一個成果。但有時,可能需要你說服其他團隊成員或管理層認爲性能分析是值得的——因爲暫時看不到效果。

但總的來說,我建議首先處理最重要的性能問題。這將爲你提供最大的性能改進,而且可能再也不需要去解決其中一些爲了滿足性能需求的問題。

常見的性能調整技巧到此結束。下面讓我們仔細看看一些特定於Java的技巧。

5.使用StringBuilder以編程方式連接String

有很多不同的選項來連接Java中的String。例如,你可以使用簡單的+或+ =,以及StringBuffer或StringBuilder。

那麼,你應該選擇哪種方法?

答案取決於連接String的代碼。如果你是以編程方式添加新內容到String中,例如在for循環中,那麼你應該使用StringBuilder。它很容易使用,並提供比StringBuffer更好的性能。但請記住,與StringBuffer相比,StringBuilder不是線程安全的,可能不適合所有用例。

你只需要實例化一個新的StringBuilder並調用append方法來向String中添加一個新的部分。在你添加了所有的部分之後,你就可以調用toString()方法來檢索連接的String。

下面的代碼片段顯示了一個簡單的例子。在每次迭代期間,這個循環將i轉換爲一個String,並將它與一個空格一起添加到StringBuilder sb中。所以,最後,這段代碼將在日誌文件中寫入“This is a test0 1 2 3 4 5 6 7 8 9”。

StringBuilder sb = new StringBuilder(“This is a test”);
for (int i=0; i<10; i++) {
    sb.append(i);
    sb.append(” “);
}
log.info(sb.toString());

正如在代碼片段中看到的那樣,你可以將String的第一個元素提供給構造方法。這將創建一個新的StringBuilder,新的StringBuilder包含提供的String和16個額外字符的容量。當你向StringBuilder添加更多字符時,JVM將動態增加StringBuilder的大小。

如果你已經知道你的String將包含多少個字符,則可以將該數字提供給不同的構造方法以實例化具有定義容量的StringBuilder。這進一步提高了效率,因爲它不需要動態擴展其容量。

6.使用+連接一個語句中的String

當你用Java實現你的第一個應用程序時,可能有人告訴過你不應該用+來連接String。如果你是在應用程序邏輯中連接字符串,這是正確的。字符串是不可變的,每個字符串的連接結果都存儲在一個新的String對象中。這需要額外的內存,會減慢你的應用程序,特別是如果你在一個循環內連接多個字符串的話。

在這些情況下,你應該遵循技巧5並使用StringBuilder。

但是,如果你只是將字符串分成多行來改善代碼的可讀性,那情況就不一樣了。

Query q = em.createQuery(“SELECT a.id, a.firstName, a.lastName ”
+ “FROM Author a ”
+ “WHERE a.id = :id”);

在這些情況下,你應該用一個簡單的+來連接你的字符串。Java編譯器會對此優化並在編譯時執行連接。所以,在運行時,你的代碼將只使用1個String,不需要連接。

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的StringUtils.replace方法。正如Lukas Eder在他最近的一篇博客文章中所描述的,StringUtils.replace方法遠勝Java 8的String.replace方法。

而且它只需要很小的改動。即添加Apache Commons Lang項目的Maven依賴項到應用程序pom.xml中,並將String.replace方法的所有調用替換爲StringUtils.replace方法。

// replace this
test.replace(“test”, “simple test”);
// with this
StringUtils.replace(test, “test”, “simple test”);

11.緩存昂貴的資源,如數據庫連接

緩存是避免重複執行昂貴或常用代碼片段的流行解決方案。總的思路很簡單:重複使用這些資源比反覆創建新的資源要便宜。

一個典型的例子是緩存池中的數據庫連接。新連接的創建需要時間,如果你重用現有連接,則可以避免這種情況。

你還可以在Java語言本身找到其他例子。例如,Integer類的valueOf方法緩存了-128到127之間的值。你可能會說創建一個新的Integer並不是太昂貴,但是由於它經常被使用,以至於緩存最常用的值也可以提供性能優勢。

但是,當你考慮緩存時,請記住緩存實現也會產生開銷。你需要花費額外的內存來存儲可重用資源,因此你可能需要管理緩存以使資源可訪問,以及刪除過時的資源。

所以,在開始緩存任何資源之前,請確保實施緩存是值得的,也就是說必須足夠多地使用它們。

總結

正如你所看到的,有時不需要太多工作就可以提高應用程序的性能。本文中的大部分建議只需要你稍作努力就可以將它們應用於你的代碼。

但是,最重要的還是那些與是什麼編程語言無關的技巧:

  • 在你知道必要之前不要優化
  • 使用分析器查找真正的瓶頸
  • 首先處理最大的瓶頸
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章