Java 性能調(diào)優(yōu)的 11 個實用技巧

大多數(shù)開發(fā)人員認(rèn)為性能優(yōu)化是個比較復(fù)雜的問題渐北,需要大量的經(jīng)驗和知識筛璧。是的牛欢,這并不沒有錯骡男。誠然,優(yōu)化應(yīng)用程序以獲得最好的性能并不是一件容易的事情傍睹,但這并不意味著你在沒有獲得這些經(jīng)驗和知識之前就不能做任何事隔盛。下面有幾個很容易遵循的建議和最佳實踐能夠幫你創(chuàng)建一個性能良好的應(yīng)用程序。

這些建議中的大多數(shù)都是基于Java的焰望,但是也不一定骚亿,也有一些是可以應(yīng)用于所有的應(yīng)用程序和編程語言的。在我們分享基于Java的性能調(diào)優(yōu)技巧之前熊赖,讓我們先討論一下這些通用的性能調(diào)優(yōu)技巧。

1.在必要之前虑椎,先不要優(yōu)化

這可能是最最重要的性能調(diào)優(yōu)技巧之一震鹉。你應(yīng)該遵循常見的最佳實踐,并嘗試有效地實現(xiàn)你的用例捆姜。但這并不意味著在證明它是必要之前传趾,替換任何標(biāo)準(zhǔn)庫或構(gòu)建復(fù)雜的優(yōu)化。

在大多數(shù)情況下泥技,過早的優(yōu)化占用了大量的時間浆兰,使得代碼難以讀取和維護(hù)。更糟糕的是珊豹,這些優(yōu)化通常不會帶來任何好處簸呈,因為你花費(fèi)了大量時間來優(yōu)化應(yīng)用程序的非關(guān)鍵部分。

那么店茶,你如何證明你需要優(yōu)化某些東西呢?

首先蜕便,你需要確定應(yīng)用程序代碼的速度,例如贩幻,為所有API調(diào)用指定一個最大響應(yīng)時間轿腺,或者指定在特定時間范圍內(nèi)導(dǎo)入的記錄數(shù)量。完成之后丛楚,你可以度量應(yīng)用程序的哪些部分太慢而需要改進(jìn)族壳。當(dāng)這樣做之后,那么請繼續(xù)看第二個調(diào)優(yōu)技巧趣些。

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

在你遵循第一條建議仿荆,并確定你的應(yīng)用程序的某些部分的確需要改進(jìn)之后,問自己從哪里開始?

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

你可以看一下你的代碼,從看起來可疑或者你覺得它可能會產(chǎn)生問題的部分開始赖歌。

或者使用分析器枉圃,獲取代碼中每個部分的行為和性能的詳細(xì)信息。

至于為什么應(yīng)該總是遵循第二種方法庐冯。

答案應(yīng)該很明顯孽亲,基于分析器的方法能讓你更好地理解代碼的性能含義,并允許你關(guān)注最關(guān)鍵的部分展父。如果你曾經(jīng)使用過分析器返劲,你將會驚訝于代碼的哪些部分造成了性能問題。然而栖茉,很多時候篮绿,你的第一次猜想會把你引向錯誤的方向。

3 .為整個應(yīng)用程序創(chuàng)建性能測試套件

這是另一個幫助你避免許多意想不到問題的一般技巧吕漂,這些問題通常發(fā)生在性能改進(jìn)部署到生產(chǎn)環(huán)境之后亲配。你應(yīng)該經(jīng)常定義測試整個應(yīng)用程序的性能測試套件,并在你完成性能改進(jìn)之前和之后運(yùn)行它惶凝。

這些額外的測試運(yùn)行將幫助你識別更改的功能和性能方面的影響吼虎,并確保你不會發(fā)布一個弊大于利的更新。如果你的任務(wù)運(yùn)行于應(yīng)用程序的多個不同部分比如數(shù)據(jù)庫或緩存苍鲜,這一點(diǎn)尤其重要思灰。

4.首先解決最大的瓶頸問題

在創(chuàng)建了測試套件并使用分析器對應(yīng)用程序進(jìn)行分析之后,你就有了一個需要提高性能的問題列表混滔,這很好洒疚,但它仍然不能回答你應(yīng)該從哪里開始的問題。你可以從那些可以快速搞定的開始坯屿,亦或者從最重要的問題開始油湖。

當(dāng)然前者很誘人,因為這很快就能出結(jié)果愿伴。有時肺魁,可能需要說服其他團(tuán)隊成員或你的管理層,性能分析是值得的隔节。

但總的來說鹅经,我建議首先著手處理最重要的性能問題。這將為你提供最大的性能改進(jìn)怎诫,而且你可能只需要修復(fù)這些問題中的幾個就可以解決你的性能需求瘾晃。

在了解通用性能調(diào)優(yōu)技巧之后,讓我們再來仔細(xì)看看一些特定于Java的調(diào)優(yōu)技巧幻妓。

5.使用StringBuilder以編程方式連接字符串

在Java中有許多不同的連接字符串的選項蹦误。例如劫拢,可以使用一個簡單的+或+ =、老的StringBuffer或StringBuilder强胰。

那么舱沧,你應(yīng)該選擇哪種方法呢?

答案取決于連接字符串的代碼。如果你以編程方式向字符串中添加新內(nèi)容偶洋,例如熟吏,在for循環(huán)中,你應(yīng)該使用StringBuilder玄窝。它比StringBuffer更容易使用和提供更好的性能牵寺。但是請記住,StringBuilder與StringBuffer不同恩脂,它不是線程安全的帽氓,而且可能不適合所有用例。

你只需要實例化一個新的StringBuilder俩块,并調(diào)用append方法在字符串中添加一個新的部分黎休。當(dāng)你添加了所有的部分后,可以調(diào)用toString()方法來檢索連接字符串玉凯。

下面的代碼片段展示了一個簡單的示例奋渔。在每次迭代過程中,這個循環(huán)將i轉(zhuǎn)換成一個字符串壮啊,并將其添加到StringBuilder sb的空間中,因此到最后撑蒜,這段代碼寫入“this is test0123456789”到日志文件歹啼。

1

2

3

4

5

6

StringBuilder sb = newStringBuilder(“This is a test”);?

for(inti=0; i<10; i++) {?

????sb.append(i);?

????sb.append(” “);?

}?

log.info(sb.toString());

正如在代碼片段中看到的,你可以為構(gòu)造函數(shù)方法提供字符串的第一個元素座菠。這將創(chuàng)建一個新的StringBuilder狸眼,其中包含提供的字符串和16個額外字符的容量。當(dāng)你向StringBuilder中添加更多字符時浴滴,JVM將動態(tài)地改變StringBuilder的大小拓萌。

如果你已經(jīng)知道自己的字符串包含多少字符,那么你可以向不同的構(gòu)造函數(shù)方法提供這個數(shù)字升略,以實例化一個具有被定義容量的StringBuilder微王。這進(jìn)一步提高了它的效率,因為它不需要動態(tài)擴(kuò)展它的容量品嚣。

6.在聲明中使用+連接字符串

當(dāng)你在Java中實現(xiàn)第一個應(yīng)用程序時炕倘,可能有人告訴你不應(yīng)該用+來連接字符串。如果在應(yīng)用程序邏輯中連接字符串這是正確的翰撑。字符串是不可變的罩旋,每個字符串連接的結(jié)果存儲在一個新的字符串對象中。這需要額外的內(nèi)存,并降低應(yīng)用程序的速度涨醋,特別是在循環(huán)中連接多個字符串時瓜饥。

在這些情況下,你應(yīng)該遵循tip 5并使用StringBuilder浴骂。

但如果你只是將一個字符串分解成多行來提高代碼的可讀性乓土,那就不是這樣了。

1

2

3

Query q = em.createQuery(“SELECT a.id, a.firstName, a.lastName ”?

+ “FROM Author a ”?

+ “WHERE a.id = :id”);

在這些情況下靠闭,你應(yīng)該用一個簡單的+來連接你的字符串帐我。Java編譯器將優(yōu)化它并在編譯時執(zhí)行連接。因此愧膀,在運(yùn)行時拦键,代碼只使用1個字符,不需要連接檩淋。

7.盡可能使用基本數(shù)據(jù)類型

另一種避免開銷芬为,提高應(yīng)用程序性能的快速方法就是使用原始數(shù)據(jù)類型而不是它們的包裝類。因此蟀悦,最好是使用int而不是Integer媚朦,或者是double而不是Double。這將讓JVM將值存儲在堆棧中日戈,以減少內(nèi)存消耗询张,并更有效地處理它。

8.盡量避免BigInteger和BigDecimal

由于我們已經(jīng)討論了數(shù)據(jù)類型浙炼,我們再來看下BigInteger和BigDecimal份氧。尤其是后者,由于其精度高而受歡迎弯屈。但這是有代價的蜗帜。

BigInteger和BigDecimal比簡單的long或double需要更多的內(nèi)存,并且大大降低所有的計算速度资厉。因此厅缺,如果你需要額外的精度,或者你的數(shù)字超過了一個long范圍宴偿,最好三思而后行湘捎。這可能是你在提升性能問題中唯一需要更改的地方,特別是當(dāng)你正在實現(xiàn)一個數(shù)學(xué)算法酪我。

9.首先檢查當(dāng)前日志級別

這個建議是顯而易見的消痛,但不幸的是,你會發(fā)現(xiàn)許多代碼忽略它都哭。在創(chuàng)建調(diào)試消息之前秩伞,應(yīng)該先檢查當(dāng)前日志級別逞带。

這里有兩個例子來說明你不應(yīng)該這樣做。

1

2

3

4

// 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));

在這兩種情況下纱新,你將執(zhí)行所有需要的步驟來創(chuàng)建日志消息展氓,而不知道日志框架是否使用日志消息。在創(chuàng)建調(diào)試消息之前脸爱,最好先檢查當(dāng)前日志級別遇汞。

1

2

3

4

// 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空入。但是,如果應(yīng)用程序需要大量的替換操作族檬,并且你還沒有更新到最新的Java版本歪赢,那么檢查更快和更有效的替代方案仍然是有意義的。

一個候選就是 Apache Commons Lang’s StringUtils.replace 方法单料。正如Lukas Eder在他最近的一篇博客文章中所描述的那樣埋凯,它大大超過了Java 8的String.replace 方法。

它只需要很小的改變扫尖。你只需要為Apache’s Commons Lang 項目增加一個Maven依賴項到你的應(yīng)用pom.xml白对,并用StringUtils.replace方法替換所有String.replace方法的調(diào)用。

1

2

3

4

// replace this?

test.replace(“test”, “simple test”);?

// with this?

StringUtils.replace(test, “test”, “simple test”);

11.緩存昂貴的資源换怖,比如數(shù)據(jù)庫連接

緩存是一種流行的解決方案來避免重復(fù)執(zhí)行昂貴或頻繁使用的代碼片段甩恼。一般的想法很簡單:重復(fù)使用這些資源比一次又一次地創(chuàng)建一個新的資源要便宜得多。

一個典型的例子就是在池中緩存數(shù)據(jù)庫連接沉颂。創(chuàng)建新連接需要時間媳拴,如果重用現(xiàn)有連接,則可以避免兆览。

還可以在Java語言本身中找到其他示例。例如塞关,Integer類的valueOf方法緩存了- 128和127之間的值蒜焊。你可能會說丧叽,創(chuàng)建一個新整數(shù)并不太貴,但它經(jīng)常使用,緩存最常用的值提供了性能方面的好處踩衩。

但當(dāng)你考慮緩存時,請記住淘菩,緩存實現(xiàn)也會產(chǎn)生開銷贮乳。你需要花費(fèi)額外的內(nèi)存來存儲可重用資源,因此可能需要管理你的緩存瘾婿,以使資源能夠訪問或刪除過時的資源蜻牢。

因此烤咧,在你開始緩存任何資源之前,請確保是經(jīng)常使用抢呆。

總結(jié)

正如你所看到的煮嫌,提高應(yīng)用程序的性能有時不需要做大量的工作。這篇文章中的大多數(shù)建議抱虐,其實只需要稍微的努力就可以將它們應(yīng)用到代碼中昌阿。

但通常最重要的建議是很編程語言無關(guān)的:

在你知道有必要之前,不要優(yōu)化

使用分析器來找到真正的瓶頸

首先解決最大的瓶頸問題

歡迎工作一到五年的Java工程師朋友們加入Java程序員開發(fā): 854393687

群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用恳邀、高并發(fā)懦冰、高性能及分布式、Jvm性能調(diào)優(yōu)谣沸、Spring源碼刷钢,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時間來學(xué)習(xí)提升自己鳄抒,不要再用"沒有時間“來掩飾自己思想上的懶惰闯捎!趁年輕,使勁拼许溅,給未來的自己一個交代瓤鼻!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贤重,隨后出現(xiàn)的幾起案子茬祷,更是在濱河造成了極大的恐慌,老刑警劉巖并蝗,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祭犯,死亡現(xiàn)場離奇詭異,居然都是意外死亡滚停,警方通過查閱死者的電腦和手機(jī)沃粗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來键畴,“玉大人最盅,你說我怎么就攤上這事∑鹛瑁” “怎么了涡贱?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惹想。 經(jīng)常有香客問我问词,道長,這世上最難降的妖魔是什么嘀粱? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任激挪,我火速辦了婚禮辰狡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘灌灾。我一直安慰自己搓译,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布锋喜。 她就那樣靜靜地躺著些己,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘿般。 梳的紋絲不亂的頭發(fā)上段标,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機(jī)與錄音炉奴,去河邊找鬼逼庞。 笑死,一個胖子當(dāng)著我的面吹牛瞻赶,可吹牛的內(nèi)容都是我干的赛糟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼砸逊,長吁一口氣:“原來是場噩夢啊……” “哼璧南!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起师逸,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤司倚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后篓像,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體动知,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年员辩,在試婚紗的時候發(fā)現(xiàn)自己被綠了盒粮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奠滑,死狀恐怖拆讯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情养叛,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布宰翅,位于F島的核電站弃甥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汁讼。R本人自食惡果不足惜阔墩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瓶珊。 院中可真熱鬧啸箫,春花似錦、人聲如沸伞芹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唱较。三九已至扎唾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間南缓,已是汗流浹背胸遇。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留汉形,地道東北人纸镊。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像概疆,于是被迫代替她去往敵國和親逗威。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容