通過Java 線程堆棧進(jìn)行性能瓶頸分析

改善性能意味著用更少的資源做更多的事情青灼。為了利用并發(fā)來提高系統(tǒng)性能攻泼,我們需要更有效的利用現(xiàn)有的處理器資源胖秒,這意味著我們期望使 CPU 盡可能出于忙碌狀態(tài)(當(dāng)然雷厂,并不是讓 CPU 周期出于應(yīng)付無用計算,而是讓 CPU 做有用的事情而忙)。如果程序受限于當(dāng)前的 CPU 計算能力祥款,那么我們通過增加更多的處理器或者通過集群就能提高總的性能清笨。總的來說刃跛,性能提高抠艾,需要且僅需要解決當(dāng)前的受限資源,當(dāng)前受限資源可能是:

CPU: 如果當(dāng)前 CPU 已經(jīng)能夠接近 100% 的利用率桨昙,并且代碼業(yè)務(wù)邏輯無法再簡化检号,那么說明該系統(tǒng)的性能以及達(dá)到上線,只有通過增加處理器來提高性能

其他資源:比如連接數(shù)等蛙酪∑肟粒可以修改代碼,盡量利用 CPU桂塞,可以獲得極大的性能提升

如果你的系統(tǒng)有如下的特點(diǎn)窒所,說明系統(tǒng)存在性能瓶頸:

隨著系統(tǒng)逐步增加壓力遥昧,CPU 使用率無法趨近 100%(如下圖)

持續(xù)運(yùn)行緩慢踩验。時常發(fā)現(xiàn)應(yīng)用程序運(yùn)行緩慢状飞。通過改變環(huán)境因子(負(fù)載,連接數(shù)等)也無法有效提升整體響應(yīng)時間

系統(tǒng)性能隨時間的增加逐漸下降狂打。在負(fù)載穩(wěn)定的情況下擂煞,系統(tǒng)運(yùn)行時間越長速度越慢∨肯纾可能是由于超出某個閾值范圍对省,系統(tǒng)運(yùn)行頻繁出錯從而導(dǎo)致系統(tǒng)死鎖或崩潰

系統(tǒng)性能隨負(fù)載的增加而逐漸下降。

一個好的程序晾捏,應(yīng)該是能夠充分利用 CPU 的蒿涎。如果一個程序在單 CPU 的機(jī)器上無論多大壓力都不能使 CPU 使用率接近 100%,說明這個程序設(shè)計有問題惦辛。一個系統(tǒng)的性能瓶頸分析過程大致如下:

先進(jìn)性單流程的性能瓶頸分析同仆,受限讓單流程的性能達(dá)到最優(yōu)。

進(jìn)行整體性能瓶頸分析裙品。因為單流程性能最優(yōu),不一定整個系統(tǒng)性能最優(yōu)俗或。在多線程場合下市怎,鎖爭用?給也會導(dǎo)致性能下降。

高性能在不同的應(yīng)用場合下辛慰,有不同的含義:

有的場合高性能意味著用戶速度的體驗区匠,如界面操作等

有的場合,高吞吐量意味著高性能,如短信或者彩信驰弄,系統(tǒng)更看重吞吐量麻汰,而對每一個消息的處理時間不敏感

有的場合,是二者的結(jié)合

性能調(diào)優(yōu)的終極目標(biāo)是:系統(tǒng)的 CPU 利用率接近 100%戚篙,如果 CPU 沒有被充分利用五鲫,那么有如下幾個可能:

施加的壓力不足

系統(tǒng)存在瓶頸

1 常見的性能瓶頸

1.1 由于不恰當(dāng)?shù)耐綄?dǎo)致的資源爭用

1.1.1 不相關(guān)的兩個函數(shù),公用了一個鎖岔擂,或者不同的共享變量共用了同一個鎖位喂,無謂地制造出了資源爭用

下面是一種常見的錯誤

classMyClass{? ? Object sharedObj;synchronizedfun1() {...}//? ? 訪問共享變量 sharedObjsynchronizedfun2() {...}//? ? 訪問共享變量 sharedObjsynchronizedfun3() {...}//? ? 不訪問共享變量? sharedObjsynchronizedfun4() {...}//? ? 不訪問共享變量? sharedObjsynchronizedfun5() {...}//? ? 不訪問共享變量? sharedObj}

上面的代碼將 synchronized 加在類的每一個方法上面,違背了保護(hù)什么鎖什么的原則乱灵。對于無共享資源的方法塑崖,使用了同一個鎖,人為造成了不必要的等待痛倚。Java 缺省提供了 this 鎖规婆,這樣很多人喜歡直接在方法上使用 synchronized 加鎖,很多情況下這樣做是不恰當(dāng)?shù)牟跷龋绻豢紤]清楚就這樣做抒蚜,很容易造成鎖粒度過大:

兩個不相干的方法(沒有使用同一個共享變量),共用了 this 鎖颠区,導(dǎo)致人為的資源競爭

即使一個方法中的代碼也不是處處需要鎖保護(hù)的削锰。如果整個方法使用了 synchronized,那么很可能就把 synchronized 的作用域給人為擴(kuò)大了毕莱。在方法級別上加鎖器贩,是一種粗獷的鎖使用習(xí)慣。

上面的代碼應(yīng)該變成下面

classMyClass{Object sharedObj;synchronizedfun1() {...}//? ? 訪問共享變量 sharedObjsynchronizedfun2() {...}//? ? 訪問共享變量 sharedObjfun3() {...}//? ? 不訪問共享變量? sharedObjfun4() {...}//? ? 不訪問共享變量? sharedObjfun5() {...}//? ? 不訪問共享變量? sharedObj}

1.1.2 鎖的粒度過大朋截,對共享資源訪問完成后蛹稍,沒有將后續(xù)的代碼放在synchronized 同步代碼塊之外

這樣會導(dǎo)致當(dāng)前線程占用鎖的時間過長,其他需要鎖的線程只能等待部服,最終導(dǎo)致性能受到極大影響

voidfun1(){synchronized(lock) {? ? ......//正在訪問共享資源......//做其他耗時操作唆姐,但這些耗時操作與共享資源無關(guān)}}

上面的代碼,會導(dǎo)致一個線程長時間占有鎖廓八,而在這么長的時間里其他線程只能等待奉芦,這種寫法在不同的場合下有不同的提升余地:

單 CPU 場合 將耗時操作拿到同步塊之外,有的情況下可以提升性能剧蹂,有的場合則不能:

同步塊的耗時代碼是 CPU 密集型代碼(純 CPU 運(yùn)算等)声功,不存在磁盤 IO/網(wǎng)絡(luò) IO 等低 CPU 消耗的代碼,這種情況下宠叼,由于 CPU 執(zhí)行這段代碼是 100% 的使用率先巴,因此縮小同步塊也不會帶來任何性能上的提升。但是,同時縮小同步塊也不會帶來性能上的下降

同步塊中的耗時代碼屬于磁盤/網(wǎng)絡(luò) IO等低 CPU 消耗的代碼伸蚯,當(dāng)當(dāng)前線程正在執(zhí)行不消耗 CPU 的代碼時摩渺,這時候 CPU 是空閑的,如果此時讓 CPU 忙起來剂邮,可以帶來整體性能上的提升摇幻,所以在這種場景下,將耗時操作的代碼放在同步之外抗斤,肯定是可以提高整個性能的(囚企?)

多 CPU 場合 將耗時的操作拿到同步塊之外,總是可以提升性能

同步塊的耗時代碼是 CPU 密集型代碼(純 CPU 運(yùn)算等)瑞眼,不存在磁盤 IO/網(wǎng)絡(luò) IO 等低 CPU 消耗的代碼龙宏,這種情況下,由于是多 CPU伤疙,其他 CPU也許是空閑的银酗,因此縮小同步塊可以讓其他線程馬上得到執(zhí)行這段代碼,可以帶來性能的提升

同步塊中的耗時代碼屬于磁盤/網(wǎng)絡(luò) IO等低 CPU 消耗的代碼徒像,當(dāng)當(dāng)前線程正在執(zhí)行不消耗 CPU 的代碼時黍特,這時候總有 CPU 是空閑的,如果此時讓 CPU 忙起來锯蛀,可以帶來整體性能上的提升灭衷,所以在這種場景下,將耗時操作的代碼放在同步塊之外旁涤,肯定是可以提高整個性能的

不管如何翔曲,縮小同步范圍,對系統(tǒng)沒有任何不好的影響劈愚,大多數(shù)情況下瞳遍,會帶來性能的提升,所以一定要縮小同步范圍菌羽,因此上面的代碼應(yīng)該改為

voidfun1(){synchronized(lock) {......//正在訪問共享資源}......//做其他耗時操作掠械,但這些耗時操作與共享資源無關(guān)}

1.1.3 其他問題

Sleep 的濫用,尤其是輪詢中使用 sleep注祖,會讓用戶明顯感覺到延遲猾蒂,可以修改為 notify 和 wait

String + 的濫用,每次 + 都會產(chǎn)生一個臨時對象是晨,并有數(shù)據(jù)的拷貝

不恰當(dāng)?shù)木€程模型

效率地下的 SQL 語句或者不恰當(dāng)?shù)臄?shù)據(jù)庫設(shè)計

不恰當(dāng)?shù)?GC 參數(shù)設(shè)置導(dǎo)致的性能低下

線程數(shù)量不足

內(nèi)存泄漏導(dǎo)致的頻繁 GC

2.2 性能瓶頸分析的手段和工具

上面提到的這些原因形成的性能瓶頸婚夫,都可以通過線程堆棧分析,找到根本原因署鸡。

2.2.1 如何去模擬,發(fā)現(xiàn)性能瓶頸

性能瓶頸的幾個特征:

當(dāng)前的性能瓶頸只有一處,只有當(dāng)解決了這一處靴庆,才知道下一處时捌。沒有解決當(dāng)前性能瓶頸,下一處性能瓶頸是不會出現(xiàn)的炉抒。如下圖所示奢讨,第二段是瓶頸,解決第二段的瓶頸后焰薄,第一段就變成了瓶頸拿诸,如此反復(fù)找到所有的性能瓶頸

性能瓶頸是動態(tài)的,低負(fù)載下不是瓶頸的地方塞茅,高負(fù)載下可能成為瓶頸亩码。由于 JProfile 等性能剖析工具依附在 JVM 上帶來的開銷,使系統(tǒng)根本就無法達(dá)到該瓶頸出現(xiàn)時需要的性能野瘦,因此在這種場景下線程堆棧分析才是一個真正有效的方法

鑒于性能瓶頸的以上特點(diǎn)描沟,進(jìn)行性能模擬的時候,一定要使用比系統(tǒng)當(dāng)前稍高的壓力下進(jìn)行模擬鞭光,否則性能瓶頸不會出現(xiàn)吏廉。具體步驟如下:

2.2.2 如何通過線程堆棧識別性能瓶頸

通過線程堆棧,可以很容易的識別多線程場合下高負(fù)載的時候才會出現(xiàn)的性能瓶頸惰许。一旦一個系統(tǒng)出現(xiàn)性能瓶頸席覆,最重要的就是識別性能瓶頸,然后根據(jù)識別的性能瓶頸進(jìn)行修改汹买。一般多線程系統(tǒng)佩伤,先按照線程的功能進(jìn)行歸類(組),把執(zhí)行相同功能代碼的線程作為一組進(jìn)行分析卦睹。當(dāng)使用堆棧進(jìn)行分析的時候畦戒,以這一組線程進(jìn)行統(tǒng)計學(xué)分析。如果一個線程池為不同的功能代碼服務(wù)结序,那么將整個線程池的線程作為一組進(jìn)行分析即可障斋。

軟件的多線程技術(shù)以及高并發(fā)問題是程序員繞不開的話題,想要了解更多多線程知識點(diǎn)的徐鹤,可以關(guān)注我一下垃环,另外順便給大家推薦一個交流學(xué)習(xí)群:650385180,里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring返敬,MyBatis遂庄,Netty源碼分析,高并發(fā)劲赠、高性能涛目、分布式秸谢、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識體系霹肝,以下的知識腦圖也是在群里面獲取的估蹄。

一般一個系統(tǒng)一旦出現(xiàn)性能瓶頸,從堆棧上分析沫换,有如下三種最為典型的堆棧特征:

絕大多數(shù)線程的堆棧都表現(xiàn)為在同一個調(diào)用上下文臭蚁,且只剩下非常少的空閑線程⊙渡停可能的原因如下:

線程的數(shù)量過少

鎖的粒度過大導(dǎo)致的鎖競爭

資源競爭

鎖范圍中有大量耗時操作

遠(yuǎn)程通信的對方處理緩慢

絕大多數(shù)線程出于等待狀態(tài)垮兑,只有幾個工作的線程,總體性能上不去漱挎∠登梗可能的原因是,系統(tǒng)存在關(guān)鍵路徑识樱,關(guān)鍵路徑已經(jīng)達(dá)到瓶頸

線程總的數(shù)量很少(有些線程池的實現(xiàn)是按需創(chuàng)建線程嗤无,可能程序中創(chuàng)建線程

2.2.3 其他提高性能的方法

減少鎖的粒度,比如 ConcurrentHashMap 的實現(xiàn)默認(rèn)使用 16 個鎖的 Array(有一個副作用:鎖整個容器會很費(fèi)力怜庸,可以添加一個全局鎖)

2.2.4 性能調(diào)優(yōu)的終結(jié)條件

性能調(diào)優(yōu)總有一個終止條件当犯,如果系統(tǒng)滿足如下兩個條件,即可終止:

算法足夠優(yōu)化

沒有線程/資源的使用不當(dāng)而導(dǎo)致的 CPU 利用不足

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末割疾,一起剝皮案震驚了整個濱河市嚎卫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宏榕,老刑警劉巖拓诸,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異麻昼,居然都是意外死亡奠支,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門抚芦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倍谜,“玉大人,你說我怎么就攤上這事叉抡《蓿” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵褥民,是天一觀的道長季春。 經(jīng)常有香客問我消返,道長耘拇,這世上最難降的妖魔是什么侦锯? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任驼鞭,我火速辦了婚禮,結(jié)果婚禮上译隘,老公的妹妹穿的比我還像新娘亲桥。我一直安慰自己,他們只是感情好题篷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布番枚。 她就那樣靜靜地躺著葫笼,像睡著了一般拗馒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洋丐,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天友绝,我揣著相機(jī)與錄音迁客,去河邊找鬼哲泊。 笑死催蝗,一個胖子當(dāng)著我的面吹牛丙号,可吹牛的內(nèi)容都是我干的缰冤。 我是一名探鬼主播棉浸,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼刺彩,長吁一口氣:“原來是場噩夢啊……” “哼创倔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霸妹,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎台盯,沒想到半個月后爷恳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體温亲,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栈虚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年魂务,在試婚紗的時候發(fā)現(xiàn)自己被綠了粘姜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孤紧。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡号显,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蔑歌,到底是詐尸還是另有隱情次屠,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響凛俱,放射性物質(zhì)發(fā)生泄漏料饥。R本人自食惡果不足惜岸啡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一巡蘸、第九天 我趴在偏房一處隱蔽的房頂上張望悦荒。 院中可真熱鬧,春花似錦境氢、人聲如沸萍聊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至匕累,卻和暖如春默伍,著一層夾襖步出監(jiān)牢的瞬間也糊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虑省,地道東北人僧凰。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓伪节,卻偏偏與公主長得像绩鸣,于是被迫代替她去往敵國和親全闷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,395評論 8 265
  • 前言:雖然自己平時都在用多線程局服,也能完成基本的工作需求淫奔,但總覺得,還是對線程沒有一個系統(tǒng)的概念鸭丛,所以,查閱了一些資...
    justCode_閱讀 708評論 0 9
  • 關(guān)于時光的事兒。 昨天聽了一個聽書的內(nèi)容是關(guān)于海倫凱勒的看政,主要是關(guān)于她如何學(xué)習(xí)語言文字的事情抄罕。今天上課一天的和動力...
    祥祥布魯斯閱讀 282評論 0 0
  • 我屬雞嚷兔,他屬狗做入,也許是別人常說的"雞犬不寧"吧!我和前夫在一起二十年,也走不到對方的心里,終于在三年前彩郊,心平氣和的...
    晗君閱讀 215評論 0 0
  • 很感謝自己年輕時候所犯的錯誤秫逝,它們帶給了我成長违帆,讓我可以不斷地反思自己金蜀,讓我可以遇見最好的自己渊抄。 第一個就是:我一...
    韓旭東閱讀 291評論 1 3