1)盡量指定類、方法的final修飾符。帶有final修飾符的類是不可派生的,Java編譯器會(huì)尋找機(jī)會(huì)內(nèi)聯(lián)所有的final方法,內(nèi)聯(lián)對(duì)于提升Java運(yùn)行效率作用重大,此舉能夠使性能平均提高50%。
我們提供的服務(wù)有:成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、上街ssl等。為成百上千家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的上街網(wǎng)站制作公司
2)盡量重用對(duì)象。由于Java虛擬機(jī)不僅要花時(shí)間生成對(duì)象,以后可能還需要花時(shí)間對(duì)這些對(duì)象進(jìn)行垃圾回收和處理,因此生成過多的對(duì)象將會(huì)給程序的性能帶來很大的影響。
3)盡可能使用局部變量。調(diào)用方法時(shí)傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時(shí)變量都保存在棧中速度較快,其他變量,如靜態(tài)變量、實(shí)例變量等,都在堆中創(chuàng)建速度較慢。
4)慎用異常。異常對(duì)性能不利,只要有異常被拋出,Java虛擬機(jī)就必須調(diào)整調(diào)用堆棧,因?yàn)樵谔幚磉^程中創(chuàng)建了一個(gè)新的對(duì)象。異常只能用于錯(cuò)誤處理,不應(yīng)該用來控制程序流程。
5)乘法和除法使用移位操作。用移位操作可以極大地提高性能,因?yàn)樵谟?jì)算機(jī)底層,對(duì)位的操作是最方便、最快的,但是移位操作雖然快,可能會(huì)使代碼不太好理解,因此最好加上相應(yīng)的注釋。
6)盡量使用HashMap、ArrayList、StringBuilder,除非線程安全需要,否則不推薦使用 Hashtable、Vector、StringBuffer,后三者由于使用同步機(jī)制而導(dǎo)致了性能開銷。
盡量在合適的場(chǎng)合使用單例。使用單例可以減輕加載的負(fù)擔(dān)、縮短加載的時(shí)間、提高加載的效率,但并不是所有地方都適用于單例。
//SQL語(yǔ)句
12 var_dump($sql);
13 $res = mysql_query($sql);
14 $arr = array();
15 //吧結(jié)果存入數(shù)組 并記錄數(shù)組長(zhǎng)度
16 $count = 0;
17 while($data = mysql_fetch_array($res)){
18 $arr[$count] = $data;
19 $count++;
該參數(shù)值一直很高,且如果在 Physical Disk 計(jì)數(shù)器中,只有%Disk time 比較大,其他值都比較適中,硬盤可能會(huì)是瓶頸。若幾個(gè)值都比較大, 那么硬盤不是瓶頸。若數(shù)值持續(xù)超過80%,則可能是內(nèi)存泄露。如果 Physical Disk 計(jì)數(shù)器的值很高時(shí)該計(jì)數(shù)器的值(Processor%Privileged Time)也一直很高, 則考慮使用速度更快或效率更高的磁盤子系統(tǒng)。 Disk sec/Transfer 一般來說,該數(shù)值小于15ms為最好,介于15-30ms之間為良好,30-60ms之間為可以接受,超過60ms則需要考慮更換硬盤或是硬盤的RAID方式了。
我們可以從三個(gè)方面分析:堆棧的局部信息,一次堆棧的統(tǒng)計(jì)信息,多個(gè)堆棧的對(duì)比信息。
從一次的堆棧信息中,我們可以直接獲取以下信息:
每一個(gè)線程的調(diào)用關(guān)系,當(dāng)前線程在調(diào)用哪些函數(shù)
每個(gè)線程的當(dāng)前狀態(tài),持有哪些鎖,在等待哪些鎖?
從一次堆棧信息中,我們還可以統(tǒng)計(jì)以下信息:
是否有很多線程都在等待同一個(gè)鎖,說明這個(gè)系統(tǒng)存在性能瓶頸,導(dǎo)致了鎖競(jìng)爭(zhēng)
當(dāng)前線程的總數(shù)量
大多數(shù)線程在干什么,在執(zhí)行什么代碼?
從多次的堆棧信息中,可以得到以下信息:
一個(gè)線程是否長(zhǎng)期執(zhí)行,如果每次打印堆棧某個(gè)線程一直處于同樣的調(diào)用上下文中,那么說明這個(gè)線程一直執(zhí)行這段代碼,此時(shí)要根據(jù)代碼邏輯檢查,是否合理。
某個(gè)線程是否長(zhǎng)期存在獲取不到鎖的情況,線程是否永遠(yuǎn)得不到喚醒,如果某一個(gè)線程一直等在一個(gè)鎖,就要檢查占用這個(gè)鎖的線程為什么不釋放。
如果說打印一次線程堆棧是平面,那么打印多次就是立體了,我們可以看到一段運(yùn)行的情況。
接下來從以下幾個(gè)方面分析:
線程死鎖分析
java代碼導(dǎo)致的cpu過高分析
死循環(huán)分析
資源不足分析性能瓶頸分析
線程死鎖分析
線程死鎖的原因:當(dāng)兩個(gè)或者多個(gè)線程正在等待被對(duì)方占用的鎖,死鎖就會(huì)發(fā)生。在時(shí)間0的時(shí)候,線程0占用lock0鎖,線程1占用lock1鎖。在時(shí)間1,進(jìn)行了其它操作,在時(shí)間2,線程0,1企圖持有對(duì)方的鎖,但是由于2個(gè)鎖已經(jīng)在時(shí)間0被鎖住,所以只能等待釋放。由于這兩個(gè)線程互相要等待被對(duì)方占有的鎖,自己才能繼續(xù),因此就造成了死鎖。
我們看以下示例:
從打印的堆棧信息可以看到第一行,found one java-level deadlock ,如果線程存在死鎖情況,堆棧會(huì)直接給出死鎖的分析結(jié)果。從堆棧信息中可以看到,TestThread2中持有鎖0x22bffb10但在等待鎖0x22bffb08,相反,TestThread2中持有鎖0x22bffb08,但在等待鎖0x22bffb10。這里說的死鎖是真正意義上的死鎖,由于代碼的錯(cuò)誤導(dǎo)致的,嚴(yán)重程度取決于線程執(zhí)行什么性質(zhì)的功能代碼,如果是關(guān)鍵功能,可能造成整個(gè)系統(tǒng)的癱瘓。死鎖的2個(gè)或者多個(gè)線程是不消耗cpu的,所以認(rèn)為死鎖會(huì)導(dǎo)致cpu100%是錯(cuò)誤的。
Java代碼死循環(huán)等導(dǎo)致的cpu過高的分析
當(dāng)系統(tǒng)負(fù)載大的時(shí)候會(huì)導(dǎo)致cpu過高,但不正確的代碼也會(huì)導(dǎo)致cpu過高,比如死循環(huán)。我們?nèi)绾螐木€程堆棧中找到死循環(huán)的線程呢?方法就是多次打印堆棧信息,通過對(duì)比前后的線程,找到一致運(yùn)行的線程。具體步驟如下:
通過上一篇文章介紹過的方法打印第一次堆棧信息
等待一定時(shí)間,再次打印第二次堆棧信息
預(yù)處理2次堆棧信息,首先排除等待狀態(tài)的線程,這種狀態(tài)的線程不消耗cpu,前面已經(jīng)講過。我們只關(guān)注runnable狀態(tài)的線程。
比較前后2次預(yù)處理后的線程,找出一段時(shí)間一直活躍的線程,如果2次堆棧信息在同一個(gè)線程處于同樣的調(diào)用上下文,就列為重點(diǎn)懷疑對(duì)象。接下來結(jié)合代碼邏輯檢查該線程執(zhí)行的上下文所對(duì)應(yīng)的代碼是不是屬于應(yīng)該長(zhǎng)期運(yùn)行的代碼。
如果通過堆棧定位沒有發(fā)現(xiàn)可疑代碼,那么cpu高可能是不恰當(dāng)?shù)膬?nèi)存設(shè)置導(dǎo)致的頻繁gc,從而導(dǎo)致的cpu過高。
資源不足等導(dǎo)致的性能下降分析
這里說的資源包括數(shù)據(jù)庫(kù)連接。大多數(shù)時(shí)候資源不足和性能瓶頸是同一類問題。當(dāng)資源不足,就會(huì)導(dǎo)致資源的競(jìng)爭(zhēng),請(qǐng)求該資源的線程會(huì)被阻塞或者掛起,自然就導(dǎo)致性能下降。系統(tǒng)對(duì)于資源,一般的設(shè)計(jì)模式是:當(dāng)需要資源的時(shí)候,獲取資源,當(dāng)不需要的時(shí)候就把資源釋放掉。如果暫時(shí)沒有可用的資源,就等待在哪里。如果有別的線程釋放資源,那么等待的線程被notify,等待的線程獲得資源繼續(xù)運(yùn)行。一般的資源設(shè)計(jì)都遵循wait/notify模式。如果資源不足,那么有大量的線程等待資源,打印的線程堆棧如果具有這個(gè)特征,說明該資源存在瓶頸。
大量的線程停在同樣的調(diào)用上下文上。
資源數(shù)量配置太小,如數(shù)據(jù)庫(kù)連接數(shù),如果系統(tǒng)壓力過大,資源不足導(dǎo)致線程不能及時(shí)獲得資源而等待在那里。
獲得資源的線程把持資源時(shí)間太久,導(dǎo)致資源不足。比如把一段和操作數(shù)據(jù)庫(kù)無關(guān)的代碼放在獲取數(shù)據(jù)庫(kù)連接和釋放數(shù)據(jù)庫(kù)連接之間。
設(shè)計(jì)不合理導(dǎo)致的資源占用太久,比如sql沒有加索引導(dǎo)致執(zhí)行sql太慢。
資源用完后,沒有關(guān)閉導(dǎo)致資源泄漏或者減少。
資源不足往往表現(xiàn)出一個(gè)現(xiàn)象是系統(tǒng)越來越慢,并最終停止響應(yīng),超時(shí)。
多個(gè)鎖導(dǎo)致的鎖鏈分析
很多線程在等待不同的鎖,有的鎖競(jìng)爭(zhēng)可能是由于另一鎖對(duì)象競(jìng)爭(zhēng)導(dǎo)致,這時(shí)候需要找到根源。
看到有40多個(gè)線程在等待鎖0xbef17078,首先找到已經(jīng)占有這把鎖的線程thread-196
看到thread-196占有鎖,0xbef17078,但又在等待鎖0xbc7b4110,那么此時(shí)需要再找占有0xbc7b4110這個(gè)鎖的線程,即thread-609。
那么占有鎖0xbc7b4110的線程是問題的根源,下一步就是查到底為什么這個(gè)線程長(zhǎng)時(shí)間占有這個(gè)鎖??赡艿脑蚴浅钟羞@把鎖的線程正在執(zhí)行的代碼性能比較低,導(dǎo)致占有時(shí)間過長(zhǎng)。
java低代碼開發(fā)平臺(tái),能快速開發(fā),節(jié)省人工成本,提高開發(fā)效率。
國(guó)內(nèi)快速開發(fā)平臺(tái)如目前流行的低代碼快速開發(fā)平臺(tái)(如有天翎 ?,普元,天縱,等廠家)myApps微服務(wù)架構(gòu),多租戶模式,門戶集成,單點(diǎn)登錄,移動(dòng)端(企業(yè)微信,釘釘,APP),功能模板都是可視化配置(如表單引擎,視圖引擎,流程引擎,報(bào)表引擎,像操作word或Excel,擴(kuò)展性強(qiáng),提供源代碼,支持國(guó)產(chǎn)數(shù)據(jù)庫(kù),操作系統(tǒng)((瀚高,達(dá)夢(mèng),統(tǒng)信,中標(biāo)麒麟等)如天翎java平臺(tái)后端核心框架:Spring MVC+SpringBoot2.X,視圖框架:Spring MVC,緩存框架:Ehcache+Redis,持久層框架:Hibernate+JDBC+File System Serilizable,安全框架:Spring security+antisamyt等
可供程序利用的資源(內(nèi)存、CPU時(shí)間、網(wǎng)絡(luò)帶寬等)是有限的,優(yōu)化的目的就是讓程序用盡可能少的資源完成預(yù)定的任務(wù)。優(yōu)化通常包含兩方面的內(nèi)容:減小代碼的體積,提高代碼的運(yùn)行效率。本文討論的主要是如何提高代碼的效率。
在Java程序中,性能問題的大部分原因并不在于Java語(yǔ)言,而是在于程序本身。養(yǎng)成好的代碼編寫習(xí)慣非常重要,比如正確地、巧妙地運(yùn)用java.lang.String類和java.util.Vector類,它能夠顯著地提高程序的性能。下面我們就來具體地分析一下這方面的問題。
1、 盡量指定類的final修飾符帶有final修飾符的類是不可派生的。在Java核心API中,有許多應(yīng)用final的例子,例如java.lang.String。為String類指定final防止了人們覆蓋length()方法。另外,如果指定一個(gè)類為final,則該類所有的方法都是final。Java編譯器會(huì)尋找機(jī)會(huì)內(nèi)聯(lián)(inline)所有的final方法(這和具體的編譯器實(shí)現(xiàn)有關(guān))。此舉能夠使性能平均提高50%
。
2、 盡量重用對(duì)象。特別是String 對(duì)象的使用中,出現(xiàn)字符串連接情況時(shí)應(yīng)用StringBuffer 代替。由于系統(tǒng)不僅要花時(shí)間生成對(duì)象,以后可能還需花時(shí)間對(duì)這些對(duì)象進(jìn)行垃圾回收和處理。因此,生成過多的對(duì)象將會(huì)給程序的性能帶來很大的影響。
3、 盡量使用局部變量,調(diào)用方法時(shí)傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時(shí)變量都保存在棧(Stack)中,速度較快。其他變量,如靜態(tài)變量、實(shí)例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。另外,依賴于具體的編譯器/JVM,局部變量還可能得到進(jìn)一步優(yōu)化。請(qǐng)參見《盡可能使用堆棧變量》。
4、 不要重復(fù)初始化變量 默認(rèn)情況下,調(diào)用類的構(gòu)造函數(shù)時(shí),
Java會(huì)把變量初始化成確定的值:所有的對(duì)象被設(shè)置成null,整數(shù)變量(byte、short、int、long)設(shè)置成0,float和double變量設(shè)置成0.0,邏輯值設(shè)置成false。當(dāng)一個(gè)類從另一個(gè)類派生時(shí),這一點(diǎn)尤其應(yīng)該注意,因?yàn)橛胣ew關(guān)鍵詞創(chuàng)建一個(gè)對(duì)象時(shí),構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會(huì)被自動(dòng)調(diào)用。
5、 在JAVA + ORACLE 的應(yīng)用系統(tǒng)開發(fā)中,java中內(nèi)嵌的SQL語(yǔ)句盡量使用大寫的形式,以減輕ORACLE解析器的解析負(fù)擔(dān)。
6、 Java 編程過程中,進(jìn)行數(shù)據(jù)庫(kù)連接、I/O流操作時(shí)務(wù)必小心,在使用完畢后,即使關(guān)閉以釋放資源。因?yàn)閷?duì)這些大對(duì)象的操作會(huì)造成系統(tǒng)大的開銷,稍有不慎,會(huì)導(dǎo)致嚴(yán)重的后果。
7、 由于JVM的有其自身的GC機(jī)制,不需要程序開發(fā)者的過多考慮,從一定程度上減輕了開發(fā)者負(fù)擔(dān),但同時(shí)也遺漏了隱患,過分的創(chuàng)建對(duì)象會(huì)消耗系統(tǒng)的大量?jī)?nèi)存,嚴(yán)重時(shí)會(huì)導(dǎo)致內(nèi)存泄露,因此,保證過期對(duì)象的及時(shí)回收具有重要意義。JVM回收垃圾的條件是:對(duì)象不在被引用;然而,JVM的GC并非十分的機(jī)智,即使對(duì)象滿足了垃圾回收的條件也不一定會(huì)被立即回收。所以,建議我們?cè)趯?duì)象使用完畢,應(yīng)手動(dòng)置成null。
8、 在使用同步機(jī)制時(shí),應(yīng)盡量使用方法同步代替代碼塊同步。
9、 盡量減少對(duì)變量的重復(fù)計(jì)算
例如:for(int i = 0;i list.size; i ++) {
…
}
應(yīng)替換為:
for(int i = 0,int len = list.size();i len; i ++) {
…
}
10、盡量采用lazy loading 的策略,即在需要的時(shí)候才開始創(chuàng)建。
例如: String str = “aaa”;
if(i == 1) {
list.add(str);
}
應(yīng)替換為:
if(i == 1) {
String str = “aaa”;
list.add(str);
}
11、慎用異常
異常對(duì)性能不利。拋出異常首先要?jiǎng)?chuàng)建一個(gè)新的對(duì)象。Throwable接口的構(gòu)造函數(shù)調(diào)用名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆棧,收集調(diào)用跟蹤信息。只要有異常被拋出,VM就必須調(diào)整調(diào)用堆棧,因?yàn)樵谔幚磉^程中創(chuàng)建了一個(gè)新的對(duì)象。異常只能用于錯(cuò)誤處理,不應(yīng)該用來控制程序流程。
12、不要在循環(huán)中使用:
Try {
} catch() {
}
應(yīng)把其放置在最外層。
13、StringBuffer 的使用:
StringBuffer表示了可變的、可寫的字符串。
有三個(gè)構(gòu)造方法 :
StringBuffer (); //默認(rèn)分配16個(gè)字符的空間
StringBuffer (int size); //分配size個(gè)字符的空間
StringBuffer (String str); //分配16個(gè)字符+str.length()個(gè)字符空間
你可以通過StringBuffer的構(gòu)造函數(shù)來設(shè)定它的初始化容量,這樣可以明顯地提升性能。這里提到的構(gòu)造函數(shù)是StringBuffer(int
length),length參數(shù)表示當(dāng)前的StringBuffer能保持的字符數(shù)量。你也可以使用ensureCapacity(int
minimumcapacity)方法在StringBuffer對(duì)象創(chuàng)建之后設(shè)置它的容量。首先我們看看StringBuffer的缺省行為,然后再找出一條更好的提升性能的途徑。
StringBuffer在內(nèi)部維護(hù)一個(gè)字符數(shù)組,當(dāng)你使用缺省的構(gòu)造函數(shù)來創(chuàng)建StringBuffer對(duì)象的時(shí)候,因?yàn)闆]有設(shè)置初始化字符長(zhǎng)度,StringBuffer的容量被初始化為16個(gè)字符,也就是說缺省容量就是16個(gè)字符。當(dāng)StringBuffer達(dá)到最大容量的時(shí)候,它會(huì)將自身容量增加到當(dāng)前的2倍再加2,也就是(2*舊值+2)。如果你使用缺省值,初始化之后接著往里面追加字符,在你追加到第16個(gè)字符的時(shí)候它會(huì)將容量增加到34(2*16+2),當(dāng)追加到34個(gè)字符的時(shí)候就會(huì)將容量增加到70(2*34+2)。無論何事只要StringBuffer到達(dá)它的最大容量它就不得不創(chuàng)建一個(gè)新的字符數(shù)組然后重新將舊字符和新字符都拷貝一遍――這也太昂貴了點(diǎn)。所以總是給StringBuffer設(shè)置一個(gè)合理的初始化容量值是錯(cuò)不了的,這樣會(huì)帶來立竿見影的性能增益。
StringBuffer初始化過程的調(diào)整的作用由此可見一斑。所以,使用一個(gè)合適的容量值來初始化StringBuffer永遠(yuǎn)都是一個(gè)最佳的建議。
14、合理的使用Java類 java.util.Vector。
簡(jiǎn)單地說,一個(gè)Vector就是一個(gè)java.lang.Object實(shí)例的數(shù)組。Vector與數(shù)組相似,它的元素可以通過整數(shù)形式的索引訪問。但是,Vector類型的對(duì)象在創(chuàng)建之后,對(duì)象的大小能夠根據(jù)元素的增加或者刪除而擴(kuò)展、縮小。請(qǐng)考慮下面這個(gè)向Vector加入元素的例子:
Object obj = new Object();
Vector v = new Vector(100000);
for(int I=0;
I100000; I++) { v.add(0,obj); }
除非有絕對(duì)充足的理由要求每次都把新元素插入到Vector的前面,否則上面的代碼對(duì)性能不利。在默認(rèn)構(gòu)造函數(shù)中,Vector的初始存儲(chǔ)能力是10個(gè)元素,如果新元素加入時(shí)存儲(chǔ)能力不足,則以后存儲(chǔ)能力每次加倍。Vector類就象StringBuffer類一樣,每次擴(kuò)展存儲(chǔ)能力時(shí),所有現(xiàn)有的元素都要復(fù)制到新的存儲(chǔ)空間之中。下面的代碼片段要比前面的例子快幾個(gè)數(shù)量級(jí):
Object obj = new Object();
Vector v = new Vector(100000);
for(int I=0; I100000; I++) { v.add(obj); }
同樣的規(guī)則也適用于Vector類的remove()方法。由于Vector中各個(gè)元素之間不能含有“空隙”,刪除除最后一個(gè)元素之外的任意其他元素都導(dǎo)致被刪除元素之后的元素向前移動(dòng)。也就是說,從Vector刪除最后一個(gè)元素要比刪除第一個(gè)元素“開銷”低好幾倍。
假設(shè)要從前面的Vector刪除所有元素,我們可以使用這種代碼:
for(int I=0; I100000; I++)
{
v.remove(0);
}
但是,與下面的代碼相比,前面的代碼要慢幾個(gè)數(shù)量級(jí):
for(int I=0; I100000; I++)
{
v.remove(v.size()-1);
}
從Vector類型的對(duì)象v刪除所有元素的最好方法是:
v.removeAllElements();
假設(shè)Vector類型的對(duì)象v包含字符串“Hello”??紤]下面的代碼,它要從這個(gè)Vector中刪除“Hello”字符串:
String s = "Hello";
int i = v.indexOf(s);
if(I != -1) v.remove(s);
這些代碼看起來沒什么錯(cuò)誤,但它同樣對(duì)性能不利。在這段代碼中,indexOf()方法對(duì)v進(jìn)行順序搜索尋找字符串“Hello”,remove(s)方法也要進(jìn)行同樣的順序搜索。改進(jìn)之后的版本是:
String s = "Hello";
int i = v.indexOf(s);
if(I != -1) v.remove(i);
這個(gè)版本中我們直接在remove()方法中給出待刪除元素的精確索引位置,從而避免了第二次搜索。一個(gè)更好的版本是:
String s = "Hello"; v.remove(s);
最后,我們?cè)賮砜匆粋€(gè)有關(guān)Vector類的代碼片段:
for(int I=0; I++;I v.length)
如果v包含100,000個(gè)元素,這個(gè)代碼片段將調(diào)用v.size()方法100,000次。雖然size方法是一個(gè)簡(jiǎn)單的方法,但它仍舊需要一次方法調(diào)用的開銷,至少JVM需要為它配置以及清除堆棧環(huán)境。在這里,for循環(huán)內(nèi)部的代碼不會(huì)以任何方式修改Vector類型對(duì)象v的大小,因此上面的代碼最好改寫成下面這種形式:
int size = v.size(); for(int I=0; I++;Isize)
雖然這是一個(gè)簡(jiǎn)單的改動(dòng),但它仍舊贏得了性能。畢竟,每一個(gè)CPU周期都是寶貴的。
15、當(dāng)復(fù)制大量數(shù)據(jù)時(shí),使用System.arraycopy()命令。
16、代碼重構(gòu):增強(qiáng)代碼的可讀性。
例如:
public class ShopCart {
private List carts ;
…
public void add (Object item) {
if(carts == null) {
carts = new ArrayList();
}
crts.add(item);
}
public void remove(Object item) {
if(carts. contains(item)) {
carts.remove(item);
}
}
public List getCarts() {
//返回只讀列表
return Collections.unmodifiableList(carts);
}
//不推薦這種方式
//this.getCarts().add(item);
}
17、不用new關(guān)鍵詞創(chuàng)建類的實(shí)例
用new關(guān)鍵詞創(chuàng)建類的實(shí)例時(shí),構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會(huì)被自動(dòng)調(diào)用。但如果一個(gè)對(duì)象實(shí)現(xiàn)了Cloneable接口,我們可以調(diào)用它的clone()方法。clone()方法不會(huì)調(diào)用任何類構(gòu)造函數(shù)。
在使用設(shè)計(jì)模式(Design Pattern)的場(chǎng)合,如果用Factory模式創(chuàng)建對(duì)象,則改用clone()方法創(chuàng)建新的對(duì)象實(shí)例非常簡(jiǎn)單。例如,下面是Factory模式的一個(gè)典型實(shí)現(xiàn):
public static Credit getNewCredit() {
return new Credit();
}
改進(jìn)后的代碼使用clone()方法,如下所示:
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone();
}
上面的思路對(duì)于數(shù)組處理同樣很有用。
18、乘法和除法
考慮下面的代碼:
for (val = 0; val 100000; val +=5) {
alterX = val * 8; myResult = val * 2;
}
用移位操作替代乘法操作可以極大地提高性能。下面是修改后的代碼:
for (val = 0; val 100000; val += 5) {
alterX = val 3; myResult = val 1;
}
修改后的代碼不再做乘以8的操作,而是改用等價(jià)的左移3位操作,每左移1位相當(dāng)于乘以2。相應(yīng)地,右移1位操作相當(dāng)于除以2。值得一提的是,雖然移位操作速度快,但可能使代碼比較難于理解,所以最好加上一些注釋。
19、在JSP頁(yè)面中關(guān)閉無用的會(huì)話。
一個(gè)常見的誤解是以為session在有客戶端訪問時(shí)就被創(chuàng)建,然而事實(shí)是直到某server端程序調(diào)用HttpServletRequest.getSession(true)這樣的語(yǔ)句時(shí)才被創(chuàng)建,注意如果JSP沒有顯示的使用 %@pagesession="false"% 關(guān)閉session,則JSP文件在編譯成Servlet時(shí)將會(huì)自動(dòng)加上這樣一條語(yǔ)句HttpSession
session = HttpServletRequest.getSession(true);這也是JSP中隱含的session對(duì)象的來歷。由于session會(huì)消耗內(nèi)存資源,因此,如果不打算使用session,應(yīng)該在所有的JSP中關(guān)閉它。
對(duì)于那些無需跟蹤會(huì)話狀態(tài)的頁(yè)面,關(guān)閉自動(dòng)創(chuàng)建的會(huì)話可以節(jié)省一些資源。使用如下page指令:%@ page session="false"%
20、JDBC與I/O
如果應(yīng)用程序需要訪問一個(gè)規(guī)模很大的數(shù)據(jù)集,則應(yīng)當(dāng)考慮使用塊提取方式。默認(rèn)情況下,JDBC每次提取32行數(shù)據(jù)。舉例來說,假設(shè)我們要遍歷一個(gè)5000行的記錄集,JDBC必須調(diào)用數(shù)據(jù)庫(kù)157次才能提取到全部數(shù)據(jù)。如果把塊大小改成512,則調(diào)用數(shù)據(jù)庫(kù)的次數(shù)將減少到10次。
[p][/p]21、Servlet與內(nèi)存使用
許多開發(fā)者隨意地把大量信息保存到用戶會(huì)話之中。一些時(shí)候,保存在會(huì)話中的對(duì)象沒有及時(shí)地被垃圾回收機(jī)制回收。從性能上看,典型的癥狀是用戶感到系統(tǒng)周期性地變慢,卻又不能把原因歸于任何一個(gè)具體的組件。如果監(jiān)視JVM的堆空間,它的表現(xiàn)是內(nèi)存占用不正常地大起大落。
解決這類內(nèi)存問題主要有二種辦法。第一種辦法是,在所有作用范圍為會(huì)話的Bean中實(shí)現(xiàn)HttpSessionBindingListener接口。這樣,只要實(shí)現(xiàn)valueUnbound()方法,就可以顯式地釋放Bean使用的資源。另外一種辦法就是盡快地把會(huì)話作廢。大多數(shù)應(yīng)用服務(wù)器都有設(shè)置會(huì)話作廢間隔時(shí)間的選項(xiàng)。另外,也可以用編程的方式調(diào)用會(huì)話的setMaxInactiveInterval()方法,該方法用來設(shè)定在作廢會(huì)話之前,Servlet容器允許的客戶請(qǐng)求的最大間隔時(shí)間,以秒計(jì)。
22、使用緩沖標(biāo)記
一些應(yīng)用服務(wù)器加入了面向JSP的緩沖標(biāo)記功能。例如,BEA的WebLogic Server從6.0版本開始支持這個(gè)功能,Open
Symphony工程也同樣支持這個(gè)功能。JSP緩沖標(biāo)記既能夠緩沖頁(yè)面片斷,也能夠緩沖整個(gè)頁(yè)面。當(dāng)JSP頁(yè)面執(zhí)行時(shí),如果目標(biāo)片斷已經(jīng)在緩沖之中,則生成該片斷的代碼就不用再執(zhí)行。頁(yè)面級(jí)緩沖捕獲對(duì)指定URL的請(qǐng)求,并緩沖整個(gè)結(jié)果頁(yè)面。對(duì)于購(gòu)物籃、目錄以及門戶網(wǎng)站的主頁(yè)來說,這個(gè)功能極其有用。對(duì)于這類應(yīng)用,頁(yè)面級(jí)緩沖能夠保存頁(yè)面執(zhí)行的結(jié)果,供后繼請(qǐng)求使用。
23、選擇合適的引用機(jī)制
在典型的JSP應(yīng)用系統(tǒng)中,頁(yè)頭、頁(yè)腳部分往往被抽取出來,然后根據(jù)需要引入頁(yè)頭、頁(yè)腳。當(dāng)前,在JSP頁(yè)面中引入外部資源的方法主要有兩種:include指令,以及include動(dòng)作。
include指令:例如%@ include file="copyright.html"
%。該指令在編譯時(shí)引入指定的資源。在編譯之前,帶有include指令的頁(yè)面和指定的資源被合并成一個(gè)文件。被引用的外部資源在編譯時(shí)就確定,比運(yùn)行時(shí)才確定資源更高效。
include動(dòng)作:例如jsp:include page="copyright.jsp"
/。該動(dòng)作引入指定頁(yè)面執(zhí)行后生成的結(jié)果。由于它在運(yùn)行時(shí)完成,因此對(duì)輸出結(jié)果的控制更加靈活。但時(shí),只有當(dāng)被引用的內(nèi)容頻繁地改變時(shí),或者在對(duì)主頁(yè)面的請(qǐng)求沒有出現(xiàn)之前,被引用的頁(yè)面無法確定時(shí),使用include動(dòng)作才合算。
24、及時(shí)清除不再需要的會(huì)話
為了清除不再活動(dòng)的會(huì)話,許多應(yīng)用服務(wù)器都有默認(rèn)的會(huì)話超時(shí)時(shí)間,一般為30分鐘。當(dāng)應(yīng)用服務(wù)器需要保存更多會(huì)話時(shí),如果內(nèi)存容量不足,操作系統(tǒng)會(huì)把部分內(nèi)存數(shù)據(jù)轉(zhuǎn)移到磁盤,應(yīng)用服務(wù)器也可能根據(jù)“最近最頻繁使用”(Most
Recently
Used)算法把部分不活躍的會(huì)話轉(zhuǎn)儲(chǔ)到磁盤,甚至可能拋出“內(nèi)存不足”異常。在大規(guī)模系統(tǒng)中,串行化會(huì)話的代價(jià)是很昂貴的。當(dāng)會(huì)話不再需要時(shí),應(yīng)當(dāng)及時(shí)調(diào)用HttpSession.invalidate()方法清除會(huì)話。HttpSession.invalidate()方法通??梢栽趹?yīng)用的退出頁(yè)面調(diào)用。
25、不要將數(shù)組聲明為:public static final 。
26、HashMap的遍歷效率討論
經(jīng)常遇到對(duì)HashMap中的key和value值對(duì)的遍歷操作,有如下兩種方法:MapString, String[] paraMap = new HashMapString, String[]();
................//第一個(gè)循環(huán)
SetString appFieldDefIds = paraMap.keySet();
for (String appFieldDefId : appFieldDefIds) {
String[] values = paraMap.get(appFieldDefId);
......
}
//第二個(gè)循環(huán)
for(EntryString, String[] entry : paraMap.entrySet()){
String appFieldDefId = entry.getKey();
String[] values = entry.getValue();
.......
}
第一種實(shí)現(xiàn)明顯的效率不如第二種實(shí)現(xiàn)。
分析如下 SetString appFieldDefIds = paraMap.keySet(); 是先從HashMap中取得keySet
代碼如下:
public SetK keySet() {
SetK ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSetK {
public IteratorK iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
其實(shí)就是返回一個(gè)私有類KeySet, 它是從AbstractSet繼承而來,實(shí)現(xiàn)了Set接口。
再來看看for/in循環(huán)的語(yǔ)法
for(declaration : expression_r)
statement
在執(zhí)行階段被翻譯成如下各式
for(IteratorE #i = (expression_r).iterator(); #i.hashNext();){
declaration = #i.next();
statement
}
因此在第一個(gè)for語(yǔ)句for (String appFieldDefId : appFieldDefIds) 中調(diào)用了HashMap.keySet().iterator() 而這個(gè)方法調(diào)用了newKeyIterator()
IteratorK newKeyIterator() {
return new KeyIterator();
}
private class KeyIterator extends HashIteratorK {
public K next() {
return nextEntry().getKey();
}
}
所以在for中還是調(diào)用了
在第二個(gè)循環(huán)for(EntryString, String[] entry : paraMap.entrySet())中使用的Iterator是如下的一個(gè)內(nèi)部類
private class EntryIterator extends HashIteratorMap.EntryK,V {
public Map.EntryK,V next() {
return nextEntry();
}
}
此時(shí)第一個(gè)循環(huán)得到key,第二個(gè)循環(huán)得到HashMap的Entry
效率就是從循環(huán)里面體現(xiàn)出來的第二個(gè)循環(huán)此致可以直接取key和value值
而第一個(gè)循環(huán)還是得再利用HashMap的get(Object key)來取value值
現(xiàn)在看看HashMap的get(Object key)方法
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length); //Entry[] table
EntryK,V e = table;
while (true) {
if (e == null)
return null;
if (e.hash == hash eq(k, e.key))
return e.value;
e = e.next;
}
}
其實(shí)就是再次利用Hash值取出相應(yīng)的Entry做比較得到結(jié)果,所以使用第一中循環(huán)相當(dāng)于兩次進(jìn)入HashMap的Entry中
而第二個(gè)循環(huán)取得Entry的值之后直接取key和value,效率比第一個(gè)循環(huán)高。其實(shí)按照Map的概念來看也應(yīng)該是用第二個(gè)循環(huán)好一點(diǎn),它本來就是key和value的值對(duì),將key和value分開操作在這里不是個(gè)好選擇。
說到代碼優(yōu)化,每個(gè)人或多或少都掌握一到兩種方法,但是這樣的方法對(duì)提升代碼運(yùn)行效率效果不大,最重要是對(duì)代碼的重視和了解,這樣才能提升代碼的運(yùn)行效率。在進(jìn)行代碼優(yōu)化的過程中,方法是非常重要的,多掌握幾種方法,根據(jù)代碼的不同情況選擇適合的方法進(jìn)行優(yōu)化。下面電腦培訓(xùn)為大家介紹Java代碼優(yōu)化的幾種方法。
1、使用指定類、方法的final修飾符
具有final修飾符的類不可派生。在Java核心API中,有許多最終應(yīng)用程序的例子,例如java.lang.String,整個(gè)類都是final。為類指定final修飾符允許繼承類,并且為方法指定final修飾符允許覆蓋該方法。如果將類指定為final,IT培訓(xùn)認(rèn)為該類的所有方法都是final。Java編譯器將尋找內(nèi)聯(lián)所有最終方法的機(jī)會(huì)。內(nèi)聯(lián)對(duì)于提高Java操作的效率非常重要。這可以將性能平均提高50%。
2、重用對(duì)象
String對(duì)象的使用是非常重要的,StringBuilder/StringBuffer并不是字符串連接。由于Java虛擬機(jī)需要時(shí)間來生成對(duì)象,所以將來垃圾收集和處理這些對(duì)象可能需要一些時(shí)間。因此,生成太多對(duì)象將對(duì)程序的性能產(chǎn)生很大影響。
3、使用局部變量
調(diào)用方法時(shí)傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時(shí)變量都保存在堆棧中,速度更快。其他變量(如靜態(tài)變量和實(shí)例變量)在堆中創(chuàng)建并且速度較慢。此外,昆明北大青鳥發(fā)現(xiàn)在堆棧中創(chuàng)建的變量,當(dāng)方法完成運(yùn)行時(shí),內(nèi)容消失,不需要進(jìn)行額外的垃圾收集。
4、及時(shí)關(guān)閉流
在Java編程過程中,在執(zhí)行數(shù)據(jù)庫(kù)連接和I/O流操作時(shí)要小心。使用后,北大青鳥云南嘉薈校區(qū)官網(wǎng)建議應(yīng)及時(shí)關(guān)閉以釋放資源。因?yàn)檫@些大型物體的操作會(huì)導(dǎo)致系統(tǒng)的大量開銷,稍微粗心會(huì)導(dǎo)致嚴(yán)重的后果。
網(wǎng)站題目:定位低性能java代碼 java 定位cpu過高
瀏覽地址:http://www.rwnh.cn/article26/doohccg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁(yè)設(shè)計(jì)公司、建站公司、Google、微信公眾號(hào)、網(wǎng)站收錄、網(wǎng)站導(dǎo)航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)