11個簡單的Java性能調優技巧,傻瓜都能學會!

大多數開發人員理所固然地覺得性能優化很複雜,須要大量的經驗和知識。好吧,不能說這是徹底錯誤的。優化應用程序以得到最佳性能不是一件容易的事情。可是,這並不意味着若是你不具有這些知識,就不能作任何事情。程序員

這裏有11個易於遵循的建議和最佳實踐能夠幫助你建立一個性能良好的應用程序。算法

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

1.在你知道必要以前不要優化編程

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

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

那麼,你如何證實你須要優化一些東西呢?性能優化

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

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

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

你能夠用兩種方法來解決問題:

  • 查看你的代碼,並從看起來可疑或者你以爲可能會產生問題的部分開始。

  • 或者使用分析器並獲取有關代碼每一個部分的行爲和性能的詳細信息。

但願不須要我解釋爲何應該始終遵循第二種方法的緣由。

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

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並非太昂貴,可是因爲它常常被使用,以致於緩存最經常使用的值也能夠提供性能優點。

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

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

總結 正如你所看到的,有時不須要太多工做就能夠提升應用程序的性能。本文中的大部分建議只須要你稍做努力就能夠將它們應用於你的代碼。

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

  • 在你知道必要以前不要優化
  • 使用分析器查找真正的瓶頸
  • 首先處理最大的瓶頸
相關文章
相關標籤/搜索