Java效能調優準則

weixin_33895657發表於2017-11-10

大家在寫程式碼的時候是不是都只考慮了實現,沒有考慮效能呢?如果說你只是做業務系統的增刪改查並且業務量不大的話。這是毋庸置疑的。但是如果你在較大吞吐量和較小的資源的時候。你的程式想保持正常執行嗎? 以下是您可以採取的一些步驟來消除瓶頸,快取的技巧以及其他效能調整建議。

3143569-c64e2c29fb9a0fa1
image

請點選此處輸入圖片描述

大多數開發人員感覺效能優化是一個非常複雜的話題,並且是需要大量的經驗和知識的。 當然這說的是有道理的。 優化應用程式以獲得最佳效能不是一件容易的事情。 但是,這並不意味著如果你沒有獲得這些知識,就不能做任何事情。 有幾個易於遵循的建議和最佳實踐可以幫助您建立一個效能良好的應用程式。

這些建議中的大部分都是針對Java的。 但也有幾個與語言無關的語言,您可以將其應用於所有應用程式和程式語言。 在討論特定於Java的效能調優技巧之前,先談談其中的一些通用準則。

不要在沒有必要的時候做效能調優

這可能是最重要的效能調優的準則之一。只要你根據最佳實踐或者推薦的方法實現了你的程式就行了。沒有必要在任何時候開始討論如何優化到最佳的效能。

在大多數情況下,過早進行效能優化會佔用大量時間,並使程式碼難以閱讀和維護。更糟糕的是,這些優化通常不會帶來任何好處,因為您花費大量時間來優化應用程式的非關鍵部分。

那麼,你如何來界定你需要做效能優化了呢?

首先,您需要判斷應用程式程式碼的速度是否如預期。例如,為所有API呼叫設定一個最大響應時間,或者在特定時間範圍內要匯入的記錄數。完成之後,您可以測量應用程式的哪些部分太慢,需要改進。當你這樣做的時候,你應該看看下下一個準則。

使用分析器來查詢真正的瓶頸

在遵循第一個準則並確定了應用程式需要進行效能調優的部分後要怎麼開始下手呢?

有兩個辦法來開始我們的第一刀:

  1. 你可以看看你的程式碼,並開始看起來可疑的部分,或者你覺得可能會產生問題的部分。

  2. 或者您使用一個分析器並獲取有關您的程式碼的每個部分的行為和效能的詳細資訊。

我希望我不需要解釋為什麼你應該始終遵循第二種方法。

很明顯,基於分析器的方法可以讓您更好地理解程式碼的效能影響,並使您能夠專注於最關鍵的部分。 如果您曾經使用過一個分析器,那麼您將會記得一些情況,在這些情況下,您對程式碼的哪些部分產生了效能問題感到驚訝。 我不止一次的第一次猜測會導致我走錯了方向。

為整個應用程式建立一個效能測試SuitCase

這是另一個通用規則,可以幫助您避免將效能改進部署到生產後經常發生的許多意外問題。 您應該總是定義一個效能測試套件來測試整個應用程式,並在效能改進之前和之後執行它。

這些額外的測試執行將幫助您確定更改的功能和效能副作用,並確保不會導致造成更多損害的更新。 如果您處理由應用程式的多個不同部分使用的元件,如資料庫或快取,這一點尤其重要。

先進行最大的瓶頸上工作

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

從快速獲勝開始可能會很有吸引力,因為您可以很快顯示第一個結果。 有時候,可能有必要說服其他團隊成員或管理層認為效能分析是值得的。

但總的來說,我建議從頂層開始,首先開始處理最重要的效能問題。 這將為您提供最大的效能改進,而且您可能不需要解決這些問題中的一些以滿足您的效能要求。

足夠的一般效能調整技巧。 讓我們仔細看看一些特定於Java的效能調優細節。

使用StringBuilder來連線字串

有很多不同的選項來連線Java中的字串。例如,您可以使用簡單的+或+ =,StringBuffer或一個StringBuilder。

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

答案取決於連線字串的程式碼。如果以程式設計方式將新內容新增到字串中,例如在for迴圈中,則應使用StringBuilder。它很容易使用,並提供比StringBuffer更好的效能。但請記住,與StringBuffer相比,StringBuilder不是執行緒安全的,可能不適合所有用例。

你只需要例項化一個新的StringBuilder並呼叫append方法來向String中新增一個新的部分。而當你新增了所有的部分,你可以呼叫toString()方法來檢索連線的字串。

下面的程式碼片段顯示了一個簡單的例子。在每次迭代期間,這個迴圈將i轉換為一個String,並將它與一個空格一起新增到StringBuilder sb中。所以,最後,這段程式碼在日誌檔案中寫入“This is a test0 1 2 3 4 5 6 7 8 9”。

3143569-1dbd950e968488bb
image

請點選此處輸入圖片描述

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

如果您已經知道您的字串將包含多少個字元,則可以將該數字提供給不同的構造方法以例項化具有定義的容量的StringBuilder。 這進一步提高了效率,因為它不需要動態擴充套件其容量。

在一個語句中使用+連線字串

當你用Java實現你的第一個應用程式時,可能有人告訴過你不應該用+來連線字串。 如果您在應用程式邏輯中連線字串,這是正確的。 字串是不可變的,每個字串連線的結果都儲存在一個新的String物件中。 這需要額外的記憶體,並減慢你的應用程式,特別是如果你在一個迴圈內連線多個字串。

在這些情況下,您應該遵循上面的規則並使用StringBuilder。

但是,如果您只是將字串分成多行來改善程式碼的可讀性,情況並非如此。

3143569-ce7ae175a8f8a090
image

請點選此處輸入圖片描述

在這些情況下,你應該用一個簡單的+來連線你的字串。 您的Java編譯器將優化這個並在編譯時執行連線。 所以,在執行時,你的程式碼將只使用1個字串,不需要連線。

儘可能使用基本資料

避免任何開銷並提高應用程式效能的另一種簡便快速的方法是使用基本型別而不是其包裝類。 所以,最好使用int來代替Integer,或者使用double來代替Double。 這允許您的JVM將值儲存在棧而不是在堆中,以減少記憶體消耗,並更高效地處理它。

儘量避免使用BigInteger和BigDecimal

由於我們已經在討論資料型別,所以我們也應該快速瀏覽一下BigInteger和BigDecimal。 尤其是後者因其精確性而受歡迎。 但是這是有代價的。

BigInteger和BigDecimal需要更多的記憶體比一個long或double,並且看起來會降低所有的執行效率。 所以,如果你需要額外的精度,或者如果你的數字將超過一個長的範圍,最好三思。 這可能是您需要更改以解決效能問題的唯一方法,特別是在實施數學演算法時。

檢查當前日誌級別

這個建議應該是顯而易見的,但不幸的是,你可以找到很多忽略它的程式碼。 在建立除錯訊息之前,應該始終首先檢查當前日誌級別。 否則,您可能會建立一個字串與您的日誌訊息,將被忽略之後。

這裡有兩個例子,不建議你這樣做。

log.debug(“User [” + userName + “] called method X with [” + i + “]”);

log.debug(String.format(“User [%s] called method X with [%d]”, userName, i));

在這兩種情況下,您都將執行所有必需的步驟來建立日誌訊息,而不知道日誌框架是否將使用日誌訊息。 在建立除錯訊息之前,最好先檢查當前的日誌級別。正確的寫法應該是這樣的:

if (log.isDebugEnabled()) {

log.debug(“User [” + userName + “] called method X with [” + i + “]”);

}

使用Apache Commons的StringUtils.Replace來替代String.replace

一般來說,String.replace方法工作正常,效率很高,尤其是在使用Java 9的情況下。但是,如果您的應用程式需要大量的替換操作,並且沒有更新到最新的Java版本,那麼它仍然是有意義的 檢查更快和更有效的替代品。

一個候選項是Apache Commons Lang的StringUtils.replace方法。 正如Lukas Eder在他最近的一篇部落格文章中所描述的,它遠遠超過了Java 8的String.replace方法。

而且這隻需要很小的改動。 您需要將Apache的Commons Lang專案的Maven依賴項新增到您的應用程式pom.xml中,並將String.replace方法的所有呼叫替換為StringUtils.replace方法。

快取開銷量較大的資源,如資料庫連線等

快取是避免重複執行昂貴或經常使用的程式碼片段的常用解決方案。總的想法很簡單:重複使用這些資源比反覆建立新資源要便宜。

一個典型的例子是快取池中的資料庫連線。新連線的建立需要時間,如果您重新使用現有連線,則可以避免這種情況。

您還可以在Java語言本身中找到其他示例。 Integer類的valueOf方法一樣,例如,快取你可能會說,一個新的整數的創作是不是太昂貴-128到127之間的值,但它的使用經常是最常用的值的快取記憶體提供效能優勢。

但是,當您考慮快取時,請記住您的快取實現也會產生開銷。您需要花費額外的記憶體來儲存可重用資源,您可能需要管理快取以使資源可訪問或刪除過時的資源。

因此,在開始快取任何資源之前,請確保您經常使用它們來超過快取實施的開銷。

3143569-308ad4d94f75b912
image

請點選此處輸入圖片描述

總結

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

但其實,最重要的建議是語言無關的:

  • 不要在你知道這是必要的之前進行優化

  • 使用分析器來查詢真正的瓶頸

  • 首先處理最大的瓶頸

相關文章