10個(gè)簡(jiǎn)單的Java 性能調(diào)優(yōu)技巧

10個(gè)簡(jiǎn)單的Java 性能調(diào)優(yōu)技巧

長(zhǎng)沙一度軟件培訓(xùn)      2022-04-14 13:28:01     9

10個(gè)簡(jiǎn)單的Java 性能調(diào)優(yōu)技巧,  大部分建議是針對(duì)Java的。但也有若干建議是與語(yǔ)言無(wú)關(guān)的,可以應(yīng)用于所有應(yīng)用程序和編程語(yǔ)言。在討論專(zhuān)門(mén)針對(duì)Java的性能調(diào)優(yōu)

課程價(jià)格 請(qǐng)咨詢

上課時(shí)段: 授課校區(qū):

詳細(xì)介紹

  


大部分建議是針對(duì)Java的。但也有若干建議是與語(yǔ)言無(wú)關(guān)的,可以應(yīng)用于所有應(yīng)用程序和編程語(yǔ)言。在討論專(zhuān)門(mén)針對(duì)Java的性能調(diào)優(yōu)技巧之前,讓我們先來(lái)看看通用技巧。


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


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


  在大多數(shù)情況下,過(guò)早優(yōu)化不但會(huì)占用大量時(shí)間,而且會(huì)使代碼變得難以閱讀和維護(hù)。更糟糕的是,這些優(yōu)化通常不會(huì)帶來(lái)任何好處,因?yàn)槟慊ㄙM(fèi)大量時(shí)間來(lái)優(yōu)化的是應(yīng)用程序的非關(guān)鍵部分。


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


  首先,你需要定義應(yīng)用程序代碼的速度得多快,例如,為所有API調(diào)用指定最大響應(yīng)時(shí)間,或者指定在特定時(shí)間范圍內(nèi)要導(dǎo)入的記錄數(shù)量。在完成這些之后,你就可以測(cè)量應(yīng)用程序的哪些部分太慢需要改進(jìn)。然后,接著看第二個(gè)技巧。



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


  在你遵循第一個(gè)建議并確定了應(yīng)用程序的某些部分需要改進(jìn)后,那么從哪里開(kāi)始呢?


  你可以用兩種方法來(lái)解決問(wèn)題:


  查看你的代碼,并從看起來(lái)可疑或者你覺(jué)得可能會(huì)產(chǎn)生問(wèn)題的部分開(kāi)始。


  或者使用分析器并獲取有關(guān)代碼每個(gè)部分的行為和性能的詳細(xì)信息。


  希望不需要我解釋為什么應(yīng)該始終遵循第二種方法的原因。


  很明顯,基于分析器的方法可以讓你更好地理解代碼的性能影響,并使你能夠?qū)W⒂谧铌P(guān)鍵的部分。如果你曾使用過(guò)分析器,那么你一定記得曾經(jīng)你是多么驚訝于一下就找到了代碼的哪些部分產(chǎn)生了性能問(wèn)題。老實(shí)說(shuō),我第一次的猜測(cè)不止一次地導(dǎo)致我走錯(cuò)了方向。



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


  這是另一個(gè)通用技巧,可以幫助你避免在將性能改進(jìn)部署到生產(chǎn)后經(jīng)常會(huì)發(fā)生的許多意外問(wèn)題。你應(yīng)該總是定義一個(gè)測(cè)試整個(gè)應(yīng)用程序的性能測(cè)試套件,并在性能改進(jìn)之前和之后運(yùn)行它。


  這些額外的測(cè)試運(yùn)行將幫助你識(shí)別更改的功能和性能副作用,并確保不會(huì)導(dǎo)致弊大于利的更新。如果你工作于被應(yīng)用程序若干不同部分使用的組件,如數(shù)據(jù)庫(kù)或緩存,那么這一點(diǎn)就尤其重要。



  4、首先處理最大的瓶頸


  在創(chuàng)建測(cè)試套件并使用分析器分析應(yīng)用程序之后,你可以列出一系列需要解決以提高性能的問(wèn)題。這很好,但它仍然不能回答你應(yīng)該從哪里開(kāi)始的問(wèn)題。你可以專(zhuān)注于速效方案,或從最重要的問(wèn)題開(kāi)始。


  速效方案一開(kāi)始可能會(huì)很有吸引力,因?yàn)槟憧梢院芸祜@示第一個(gè)成果。但有時(shí),可能需要你說(shuō)服其他團(tuán)隊(duì)成員或管理層認(rèn)為性能分析是值得的——因?yàn)闀簳r(shí)看不到效果。


  但總的來(lái)說(shuō),我建議首先處理最重要的性能問(wèn)題。這將為你提供最大的性能改進(jìn),而且可能再也不需要去解決其中一些為了滿足性能需求的問(wèn)題。


  常見(jiàn)的性能調(diào)整技巧到此結(jié)束。下面讓我們仔細(xì)看看一些特定于Java的技巧。



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


  有很多不同的選項(xiàng)來(lái)連接Java中的String。例如,你可以使用簡(jiǎn)單的+或+ =,以及StringBuffer或StringBuilder。


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


  答案取決于連接String的代碼。如果你是以編程方式添加新內(nèi)容到String中,例如在for循環(huán)中,那么你應(yīng)該使用StringBuilder。它很容易使用,并提供比StringBuffer更好的性能。但請(qǐng)記住,與StringBuffer相比,StringBuilder不是線程安全的,可能不適合所有用例。


  你只需要實(shí)例化一個(gè)新的StringBuilder并調(diào)用append方法來(lái)向String中添加一個(gè)新的部分。在你添加了所有的部分之后,你就可以調(diào)用toString()方法來(lái)檢索連接的String。


  下面的代碼片段顯示了一個(gè)簡(jiǎn)單的例子。在每次迭代期間,這個(gè)循環(huán)將i轉(zhuǎn)換為一個(gè)String,并將它與一個(gè)空格一起添加到StringBuilder sb中。所以,最后,這段代碼將在日志文件中寫(xiě)入“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的第一個(gè)元素提供給構(gòu)造方法。這將創(chuàng)建一個(gè)新的StringBuilder,新的StringBuilder包含提供的String和16個(gè)額外字符的容量。當(dāng)你向StringBuilder添加更多字符時(shí),JVM將動(dòng)態(tài)增加StringBuilder的大小。


  如果你已經(jīng)知道你的String將包含多少個(gè)字符,則可以將該數(shù)字提供給不同的構(gòu)造方法以實(shí)例化具有定義容量的StringBuilder。這進(jìn)一步提高了效率,因?yàn)樗恍枰獎(jiǎng)討B(tài)擴(kuò)展其容量。



  6、使用+連接一個(gè)語(yǔ)句中的String


  當(dāng)你用Java實(shí)現(xiàn)你的第一個(gè)應(yīng)用程序時(shí),可能有人告訴過(guò)你不應(yīng)該用+來(lái)連接String。如果你是在應(yīng)用程序邏輯中連接字符串,這是正確的。字符串是不可變的,每個(gè)字符串的連接結(jié)果都存儲(chǔ)在一個(gè)新的String對(duì)象中。這需要額外的內(nèi)存,會(huì)減慢你的應(yīng)用程序,特別是如果你在一個(gè)循環(huán)內(nèi)連接多個(gè)字符串的話。


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


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


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


  + “FROM Author a ”


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


  在這些情況下,你應(yīng)該用一個(gè)簡(jiǎn)單的+來(lái)連接你的字符串。Java編譯器會(huì)對(duì)此優(yōu)化并在編譯時(shí)執(zhí)行連接。所以,在運(yùn)行時(shí),你的代碼將只使用1個(gè)String,不需要連接。



  7、盡可能使用基元


  避免任何開(kāi)銷(xiāo)并提高應(yīng)用程序性能的另一個(gè)簡(jiǎn)便而快速的方法是使用基本類(lèi)型而不是其包裝類(lèi)。所以,最好使用int來(lái)代替Integer,使用double來(lái)代替Double。這允許JVM將值存儲(chǔ)在堆棧而不是堆中以減少內(nèi)存消耗,并作出更有效的處理。



  9、試著避免BigInteger和BigDecimal


  既然我們?cè)谟懻摂?shù)據(jù)類(lèi)型,那么我們也快速瀏覽一下BigInteger和BigDecimal吧。尤其是后者因其精確性而受到大家的歡迎。但是這是有代價(jià)的。


  BigInteger和BigDecimal比簡(jiǎn)單的long或double需要更多的內(nèi)存,并且會(huì)顯著減慢所有計(jì)算。所以,你如果需要額外的精度,或者數(shù)字將超過(guò)long的范圍,那么最好三思而后行。這可能是你需要更改以解決性能問(wèn)題的唯一方法,特別是在實(shí)現(xiàn)數(shù)學(xué)算法的時(shí)候。



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


  這個(gè)建議應(yīng)該是顯而易見(jiàn)的,但不幸的是,很多程序員在寫(xiě)代碼的時(shí)候都會(huì)大多會(huì)忽略它。在你創(chuàng)建調(diào)試消息之前,始終應(yīng)該首先檢查當(dāng)前日志級(jí)別。否則,你可能會(huì)創(chuàng)建一個(gè)之后會(huì)被忽略的日志消息字符串。


  這里有兩個(gè)反面例子。


  // 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)前的日志級(jí)別。


  // do this


  if (log.isDebugEnabled()) {


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


  }


  10、使用Apache Commons StringUtils.Replace而不是String.replace


  一般來(lái)說(shuō),String.replace方法工作正常,效率很高,尤其是在使用Java 9的情況下。但是,如果你的應(yīng)用程序需要大量的替換操作,并且沒(méi)有更新到最新的Java版本,那么我們依然有必要查找更快和更有效的替代品。


  有一個(gè)備選答案是Apache Commons Lang的StringUtils.replace方法。正如Lukas Eder在他最近的一篇博客文章中所描述的,StringUtils.replace方法遠(yuǎn)勝Java 8的String.replace方法。


  而且它只需要很小的改動(dòng)。即添加Apache Commons Lang項(xiàng)目的Maven依賴項(xiàng)到應(yīng)用程序pom.xml中,并將String.replace方法的所有調(diào)用替換為StringUtils.replace方法。


  // replace this


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


  // with this


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



  11、緩存昂貴的資源,如數(shù)據(jù)庫(kù)連接


  緩存是避免重復(fù)執(zhí)行昂貴或常用代碼片段的流行解決方案??偟乃悸泛芎?jiǎn)單:重復(fù)使用這些資源比反復(fù)創(chuàng)建新的資源要便宜。


  一個(gè)典型的例子是緩存池中的數(shù)據(jù)庫(kù)連接。新連接的創(chuàng)建需要時(shí)間,如果你重用現(xiàn)有連接,則可以避免這種情況。


  你還可以在Java語(yǔ)言本身找到其他例子。例如,Integer類(lèi)的valueOf方法緩存了-128到127之間的值。你可能會(huì)說(shuō)創(chuàng)建一個(gè)新的Integer并不是太昂貴,但是由于它經(jīng)常被使用,以至于緩存最常用的值也可以提供性能優(yōu)勢(shì)。


  但是,當(dāng)你考慮緩存時(shí),請(qǐng)記住緩存實(shí)現(xiàn)也會(huì)產(chǎn)生開(kāi)銷(xiāo)。你需要花費(fèi)額外的內(nèi)存來(lái)存儲(chǔ)可重用資源,因此你可能需要管理緩存以使資源可訪問(wèn),以及刪除過(guò)時(shí)的資源。




培訓(xùn)啦提醒您:交易時(shí)請(qǐng)核實(shí)對(duì)方資質(zhì),對(duì)于過(guò)大宣傳或承諾需謹(jǐn)慎!任何要求預(yù)付定金、匯款等方式均存在風(fēng)險(xiǎn),謹(jǐn)防上當(dāng)。