90%的人會遇到性能問題葫松,如何用1行代碼快速定位瓦糕?

1. 代碼相關(guān)

遇到性能問題,首先應(yīng)該做的是檢查否與業(yè)務(wù)代碼相關(guān)——不是通過閱讀代碼解決問題进宝,而是通過日志或代碼刻坊,排除掉一些與業(yè)務(wù)代碼相關(guān)的低級錯誤。性能優(yōu)化的最佳位置党晋,是應(yīng)用內(nèi)部。

譬如,查看業(yè)務(wù)日志未玻,檢查日志內(nèi)容里是否有大量的報錯產(chǎn)生灾而,應(yīng)用層、框架層的一些性能問題扳剿,大多數(shù)都能從日志里找到端倪(日志級別設(shè)置不合理旁趟,導(dǎo)致線上瘋狂打日志);再者庇绽,檢查代碼的主要邏輯锡搜,如 for 循環(huán)的不合理使用、NPE瞧掺、正則表達式耕餐、數(shù)學(xué)計算等常見的一些問題,都可以通過簡單地修改代碼修復(fù)問題辟狈。

別動輒就把性能優(yōu)化和緩存肠缔、異步化、JVM 調(diào)優(yōu)等名詞掛鉤哼转,復(fù)雜問題可能會有簡單解明未,「二八原則」在性能優(yōu)化的領(lǐng)域里里依然有效。當(dāng)然了壹蔓,了解一些基本的「代碼常用踩坑點」趟妥,可以加速我們問題分析思路的過程,從 CPU佣蓉、內(nèi)存煮纵、JVM 等分析到的一些瓶頸點優(yōu)化思路,也有可能在代碼這里體現(xiàn)出來偏螺。

下面是一些高頻的行疏,容易造成性能問題的編碼要點。

1)正則表達式非常消耗 CPU(如貪婪模式可能會引起回溯)套像,慎用字符串的 split()酿联、replaceAll() 等方法;正則表達式表達式一定預(yù)編譯夺巩。

2)String.intern() 在低版本(Java 1.6 以及之前)的 JDK 上使用贞让,可能會造成方法區(qū)(永久代)內(nèi)存溢出。在高版本 JDK 中柳譬,如果 string pool 設(shè)置太小而緩存的字符串過多喳张,也會造成較大的性能開銷。

3)輸出異常日志的時候美澳,如果堆棧信息是明確的署隘,可以取消輸出詳細(xì)堆棧禽绪,異常堆棧的構(gòu)造是有成本的繁调。注意:同一位置拋出大量重復(fù)的堆棧信息,JIT 會將其優(yōu)化后成酱虎,直接拋出一個事先編譯好的、類型匹配的異常擂涛,異常堆棧信息就看不到了读串。

4)避免引用類型和基礎(chǔ)類型之間無謂的拆裝箱操作,請盡量保持一致撒妈,自動裝箱發(fā)生太頻繁恢暖,會非常嚴(yán)重消耗性能。

5)Stream API 的選擇狰右。復(fù)雜和并行操作杰捂,推薦使用 Stream API,可以簡化代碼挟阻,同時發(fā)揮來發(fā)揮出 CPU 多核的優(yōu)勢琼娘,如果是簡單操作或者 CPU 是單核,推薦使用顯式迭代附鸽。

6)根據(jù)業(yè)務(wù)場景脱拼,通過 ThreadPoolExecutor 手動創(chuàng)建線程池,結(jié)合任務(wù)的不同坷备,指定線程數(shù)量和隊列大小熄浓,規(guī)避資源耗盡的風(fēng)險,統(tǒng)一命名后的線程也便于后續(xù)問題排查省撑。

7)根據(jù)業(yè)務(wù)場景赌蔑,合理選擇并發(fā)容器。如選擇 Map 類型的容器時竟秫,如果對數(shù)據(jù)要求有強一致性娃惯,可使用 Hashtable 或者 「Map + 鎖」 ;讀遠(yuǎn)大于寫肥败,使用 CopyOnWriteArrayList趾浅;存取數(shù)據(jù)量小、對數(shù)據(jù)沒有強一致性的要求馒稍、變更不頻繁的皿哨,使用 ConcurrentHashMap;存取數(shù)據(jù)量大纽谒、讀寫頻繁证膨、對數(shù)據(jù)沒有強一致性的要求,使用 ConcurrentSkipListMap鼓黔。

8)鎖的優(yōu)化思路有:減少鎖的粒度央勒、循環(huán)中使用鎖粗化不见、減少鎖的持有時間(讀寫鎖的選擇)等。同時订歪,也考慮使用一些 JDK 優(yōu)化后的并發(fā)類脖祈,如對一致性要求不高的統(tǒng)計場景中肆捕,使用 LongAdder 替代 AtomicLong 進行計數(shù)刷晋,使用 ThreadLocalRandom 替代 Random 類等。

代碼層的優(yōu)化除了上面這些慎陵,還有很多就不一一列出了眼虱。我們可以觀察到,在這些要點里席纽,有一些共性的優(yōu)化思路捏悬,是可以抽取出來的,譬如:

空間換時間:使用內(nèi)存或者磁盤润梯,換取更寶貴的CPU 或者網(wǎng)絡(luò)过牙,如緩存的使用;

時間換空間:通過犧牲部分 CPU纺铭,節(jié)省內(nèi)存或者網(wǎng)絡(luò)資源寇钉,如把一次大的網(wǎng)絡(luò)傳輸變成多次;

其他諸如并行化舶赔、異步化扫倡、池化技術(shù)等。

2. CPU 相關(guān)

前面講到過竟纳,我們更應(yīng)該關(guān)注 CPU 負(fù)載撵溃,CPU 利用率高一般不是問題,CPU 負(fù)載 是判斷系統(tǒng)計算資源是否健康的關(guān)鍵依據(jù)锥累。

2.1 CPU 利用率高&&平均負(fù)載高

這種情況常見于 CPU 密集型的應(yīng)用缘挑,大量的線程處于可運行狀態(tài),I/O 很少桶略,常見的大量消耗 CPU 資源的應(yīng)用場景有:

正則操作

數(shù)學(xué)運算

序列化/反序列化

反射操作

死循環(huán)或者不合理的大量循環(huán)

基礎(chǔ)/第三方組件缺陷

排查高 CPU 占用的一般思路:通過 jstack 多次(> 5次)打印線程棧语淘,一般可以定位到消耗 CPU 較多的線程堆棧∩拘裕或者通過 Profiling 的方式(基于事件采樣或者埋點)亏娜,得到應(yīng)用在一段時間內(nèi)的 on-CPU 火焰圖,也能較快定位問題蹬挺。

還有一種可能的情況维贺,此時應(yīng)用存在頻繁的 GC (包括 Young GC、Old GC巴帮、Full GC)溯泣,這也會導(dǎo)致 CPU 利用率和負(fù)載都升高虐秋。排查思路:使用 jstat -gcutil 持續(xù)輸出當(dāng)前應(yīng)用的 GC 統(tǒng)計次數(shù)和時間。頻繁 GC 導(dǎo)致的負(fù)載升高垃沦,一般還伴隨著可用內(nèi)存不足客给,可用 free 或者 top 等命令查看下當(dāng)前機器的可用內(nèi)存大小。

CPU 利用率過高肢簿,是否有可能是 CPU 本身性能瓶頸導(dǎo)致的呢靶剑?也是有可能的〕爻洌可以進一步通過 vmstat 查看詳細(xì)的 CPU 利用率桩引。用戶態(tài) CPU 利用率(us)較高,說明用戶態(tài)進程占用了較多的 CPU收夸,如果這個值長期大于50%坑匠,應(yīng)該著重排查應(yīng)用本身的性能問題。內(nèi)核態(tài) CPU 利用率(sy)較高卧惜,說明內(nèi)核態(tài)占用了較多的 CPU厘灼,所以應(yīng)該著重排查內(nèi)核線程或者系統(tǒng)調(diào)用的性能問題。如果 us + sy 的值大于 80%咽瓷,說明 CPU 可能不足设凹。

2.2 CPU 利用率低&&平均負(fù)載高

如果CPU利用率不高,說明我們的應(yīng)用并沒有忙于計算忱详,而是在干其他的事围来。CPU 利用率低而平均負(fù)載高,常見于 I/O 密集型進程匈睁,這很容易理解监透,畢竟平均負(fù)載就是 R 狀態(tài)進程和 D 狀態(tài)進程的和,除掉了第一種航唆,就只剩下 D 狀態(tài)進程了(產(chǎn)生 D 狀態(tài)的原因一般是因為在等待 I/O胀蛮,例如磁盤 I/O、網(wǎng)絡(luò) I/O 等)糯钙。

排查&&驗證思路:使用 vmstat 1 定時輸出系統(tǒng)資源使用粪狼,觀察 %wa(iowait) 列的值,該列標(biāo)識了磁盤 I/O 等待時間在 CPU 時間片中的百分比任岸,如果這個值超過30%再榄,說明磁盤 I/O 等待嚴(yán)重,這可能是大量的磁盤隨機訪問或直接的磁盤訪問(沒有使用系統(tǒng)緩存)造成的享潜,也可能磁盤本身存在瓶頸困鸥,可以結(jié)合 iostat 或 dstat 的輸出加以驗證,如 %wa(iowait) 升高同時觀察到磁盤的讀請求很大剑按,說明可能是磁盤讀導(dǎo)致的問題疾就。

此外澜术,耗時較長的網(wǎng)絡(luò)請求(即網(wǎng)絡(luò) I/O)也會導(dǎo)致 CPU 平均負(fù)載升高,如 MySQL 慢查詢猬腰、使用 RPC 接口獲取接口數(shù)據(jù)等鸟废。這種情況的排查一般需要結(jié)合應(yīng)用本身的上下游依賴關(guān)系以及中間件埋點的 trace 日志,進行綜合分析姑荷。

2.3 CPU 上下文切換次數(shù)變高

先用 vmstat 查看系統(tǒng)的上下文切換次數(shù)盒延,然后通過 pidstat 觀察進程的自愿上下文切換(cswch)和非自愿上下文切換(nvcswch)情況。自愿上下文切換厢拭,是因為應(yīng)用內(nèi)部線程狀態(tài)發(fā)生轉(zhuǎn)換所致兰英,譬如調(diào)用 sleep()撇叁、join()供鸠、wait()等方法,或使用了 Lock 或 synchronized 鎖結(jié)構(gòu)陨闹;非自愿上下文切換楞捂,是因為線程由于被分配的時間片用完或由于執(zhí)行優(yōu)先級被調(diào)度器調(diào)度所致。

如果自愿上下文切換次數(shù)較高趋厉,意味著 CPU 存在資源獲取等待寨闹,比如說,I/O君账、內(nèi)存等系統(tǒng)資源不足等繁堡。如果是非自愿上下文切換次數(shù)較高,可能的原因是應(yīng)用內(nèi)線程數(shù)過多乡数,導(dǎo)致 CPU 時間片競爭激烈椭蹄,頻頻被系統(tǒng)強制調(diào)度,此時可以結(jié)合 jstack 統(tǒng)計的線程數(shù)和線程狀態(tài)分布加以佐證净赴。

內(nèi)存相關(guān)

前面提到绳矩,內(nèi)存分為系統(tǒng)內(nèi)存和進程內(nèi)存(含 Java 應(yīng)用進程),一般我們遇到的內(nèi)存問題玖翅,絕大多數(shù)都會落在進程內(nèi)存上翼馆,系統(tǒng)資源造成的瓶頸占比較小。對于 Java 進程金度,它自帶的內(nèi)存管理自動化地解決了兩個問題:如何給對象分配內(nèi)存以及如何回收分配給對象的內(nèi)存应媚,其核心是垃圾回收機制。

垃圾回收雖然可以有效地防止內(nèi)存泄露猜极、保證內(nèi)存的有效使用中姜,但也并不是萬能的,不合理的參數(shù)配置和代碼邏輯魔吐,依然會帶來一系列的內(nèi)存問題扎筒。此外莱找,早期的垃圾回收器,在功能性和回收效率上也不是很好嗜桌,過多的 GC 參數(shù)設(shè)置非常依賴開發(fā)人員的調(diào)優(yōu)經(jīng)驗奥溺。比如,對于最大堆內(nèi)存的不恰當(dāng)設(shè)置骨宠,可能會引發(fā)堆溢出或者堆震蕩等一系列問題浮定。

下面看看幾個常見的內(nèi)存問題分析思路。

3.1 系統(tǒng)內(nèi)存不足

Java 應(yīng)用一般都有單機或者集群的內(nèi)存水位監(jiān)控层亿,如果單機的內(nèi)存利用率大于 95%桦卒,或者集群的內(nèi)存利用率大于80%,就說明可能存在潛在的內(nèi)存問題(注:這里的內(nèi)存水位是系統(tǒng)內(nèi)存)匿又。

除了一些較極端的情況方灾,一般系統(tǒng)內(nèi)存不足,大概率是由 Java 應(yīng)用引起的碌更。使用 top 命令時裕偿,我們可以看到 Java 應(yīng)用進程的實際內(nèi)存占用,其中 RES 表示進程的常駐內(nèi)存使用痛单,VIRT 表示進程的虛擬內(nèi)存占用嘿棘,內(nèi)存大小的關(guān)系為:VIRT > RES > Java 應(yīng)用實際使用的堆大小。除了堆內(nèi)存旭绒,Java 進程整體的內(nèi)存占用鸟妙,還有方法區(qū)/元空間、JIT 緩存等挥吵,主要組成如下:

Java 應(yīng)用內(nèi)存占用 = Heap(堆區(qū))+ Code Cache(代碼緩存區(qū)) + Metaspace(元空間)+ Symbol tables(符號表)+ Thread stacks(線程棧區(qū))+ Direct buffers(堆外內(nèi)存)+ JVM structures(其他的一些 JVM 自身占用)+ Mapped files(內(nèi)存映射文件)+ Native Libraries(本地庫)+ ...

Java 進程的內(nèi)存占用重父,可以使用 jstat -gc 命令查看,輸出的指標(biāo)中可以得到當(dāng)前堆內(nèi)存各分區(qū)蔫劣、元空間的使用情況坪郭。堆外內(nèi)存的統(tǒng)計和使用情況,可以利用 NMT(Native Memory Tracking脉幢,HotSpot VM Java8 引入)獲取歪沃。線程棧使用的內(nèi)存空間很容易被忽略,雖然線程棧內(nèi)存采用的是懶加載的模式嫌松,不會直接使用 +Xss 的大小來分配內(nèi)存沪曙,但是過多的線程也會導(dǎo)致不必要的內(nèi)存占用,可以使用 jstackmem 這個腳本統(tǒng)計整體的線程占用萎羔。

系統(tǒng)內(nèi)存不足的排查思路:

1.首先使用 free 查看當(dāng)前內(nèi)存的可用空間大小液走,然后使用 vmstat 查看具體的內(nèi)存使用情況及內(nèi)存增長趨勢,這個階段一般能定位占用內(nèi)存最多的進程;

2.分析緩存 / 緩沖區(qū)的內(nèi)存使用缘眶。如果這個數(shù)值在一段時間變化不大嘱根,可以忽略。如果觀察到緩存 / 緩沖區(qū)的大小在持續(xù)升高巷懈,則可以使用 pcstat该抒、cachetop、slabtop 等工具顶燕,分析緩存 / 緩沖區(qū)的具體占用凑保;

3.排除掉緩存 / 緩沖區(qū)對系統(tǒng)內(nèi)存的影響后,如果發(fā)現(xiàn)內(nèi)存還在不斷增長涌攻,說明很有可能存在內(nèi)存泄漏欧引。

3.2 Java 內(nèi)存溢出

內(nèi)存溢出是指應(yīng)用新建一個對象實例時,所需的內(nèi)存空間大于堆的可用空間恳谎。內(nèi)存溢出的種類較多芝此,一般會在報錯日志里看到 OutOfMemoryError 關(guān)鍵字。常見內(nèi)存溢出種類及分析思路如下:

1)java.lang.OutOfMemoryError: Java heap space惠爽。原因:堆中(新生代和老年代)無法繼續(xù)分配對象了癌蓖、某些對象的引用長期被持有沒有被釋放,垃圾回收器無法回收婚肆、使用了大量的 Finalizer 對象,這些對象并不在 GC 的回收周期內(nèi)等坐慰。一般堆溢出都是由于內(nèi)存泄漏引起的较性,如果確認(rèn)沒有內(nèi)存泄漏,可以適當(dāng)通過增大堆內(nèi)存结胀。

2)java.lang.OutOfMemoryError:GC overhead limit exceeded赞咙。原因:垃圾回收器超過98%的時間用來垃圾回收,但回收不到2%的堆內(nèi)存糟港,一般是因為存在內(nèi)存泄漏或堆空間過小攀操。

3)java.lang.OutOfMemoryError: Metaspace或java.lang.OutOfMemoryError: PermGen space。排查思路:檢查是否有動態(tài)的類加載但沒有及時卸載秸抚,是否有大量的字符串常量池化速和,永久代/元空間是否設(shè)置過小等。

4)java.lang.OutOfMemoryError : unable to create new native Thread剥汤。原因:虛擬機在拓展椀叻牛空間時,無法申請到足夠的內(nèi)存空間吭敢∨鲂祝可適當(dāng)降低每個線程棧的大小以及應(yīng)用整體的線程個數(shù)。此外,系統(tǒng)里總體的進程/線程創(chuàng)建總數(shù)也受到系統(tǒng)空閑內(nèi)存和操作系統(tǒng)的限制欲低,請仔細(xì)檢查辕宏。

注:這種棧溢出,和 StackOverflowError 不同砾莱,后者是由于方法調(diào)用層次太深匾效,分配的棧內(nèi)存不夠新建棧幀導(dǎo)致。此外恤磷,還有 Swap 分區(qū)溢出面哼、本地方法棧溢出、數(shù)組分配溢出等 OutOfMemoryError 類型扫步,由于不是很常見魔策,就不一一介紹了。

3.3 Java 內(nèi)存泄漏

Java 內(nèi)存泄漏可以說是開發(fā)人員的噩夢河胎,內(nèi)存泄漏與內(nèi)存溢出不同則闯袒,后者簡單粗暴,現(xiàn)場也比較好找游岳。內(nèi)存泄漏的表現(xiàn)是:應(yīng)用運行一段時間后政敢,內(nèi)存利用率越來越高,響應(yīng)越來越慢胚迫,直到最終出現(xiàn)進程「假死」喷户。

Java 內(nèi)存泄漏可能會造成系統(tǒng)可用內(nèi)存不足、進程假死访锻、OOM 等褪尝,排查思路卻不外乎下面兩種:

通過 jmap 定期輸出堆內(nèi)對象統(tǒng)計,定位數(shù)量和大小持續(xù)增長的對象期犬;

使用 Profiler 工具對應(yīng)用進行 Profiling河哑,尋找內(nèi)存分配熱點。

此外龟虎,在堆內(nèi)存持續(xù)增長時璃谨,建議 dump 一份堆內(nèi)存的快照,后面可以基于快照做一些分析鲤妥〖淹蹋快照雖然是瞬時值,但也是有一定的意義的旭斥。

3.4 垃圾回收相關(guān)

GC(垃圾回收容达,下同)的各項指標(biāo),是衡量 Java 進程內(nèi)存使用是否健康的重要標(biāo)尺垂券。垃圾回收最核心指標(biāo):GC Pause(包括 MinorGC 和 MajorGC) 的頻率和次數(shù)花盐,以及每次回收的內(nèi)存詳情羡滑,前者可以通過 jstat 工具直接得到,后者需要分析 GC 日志算芯。需要注意的是柒昏,jstat 輸出列中的 FGC/FGCT 表示的是一次老年代垃圾回收中,出現(xiàn) GC Pause (即 Stop-the-World)的次數(shù)熙揍,譬如對于 CMS 垃圾回收器职祷,每次老年代垃圾回收這個值會增加2(初始標(biāo)記和重新標(biāo)記著兩個 Stop-the-World 的階段,這個統(tǒng)計值會是 2届囚。

什么時候需要進行 GC 調(diào)優(yōu)有梆?這取決于應(yīng)用的具體情況,譬如對響應(yīng)時間的要求意系、對吞吐量的要求泥耀、系統(tǒng)資源限制等。一些經(jīng)驗:GC 頻率和耗時大幅上升蛔添、GC Pause 平均耗時超過 500ms痰催、Full GC 執(zhí)行頻率小于1分鐘等,如果 GC 滿足上述的一些特征迎瞧,說明需要進行 GC 調(diào)優(yōu)了夸溶。

由于垃圾回收器種類繁多,針對不同的應(yīng)用凶硅,調(diào)優(yōu)策略也有所區(qū)別缝裁,因此下面介紹幾種通用的的 GC 調(diào)優(yōu)策略。

1)選擇合適的 GC 回收器咏尝。根據(jù)應(yīng)用對延遲压语、吞吐的要求,結(jié)合各垃圾回收器的特點编检,合理選用。推薦使用 G1 替換 CMS 垃圾回收器扰才,G1 的性能是在逐步優(yōu)化的允懂,在 8GB 內(nèi)存及以下的機器上,其各方面的表現(xiàn)也在趕上甚至有超越之勢衩匣。G1 調(diào)參較方便蕾总,而 CMS 垃圾回收器參數(shù)太過復(fù)雜、容易造成空間碎片化琅捏、對 CPU 消耗較高等弊端生百,也使其目前處于廢棄狀態(tài)。Java 11 里新引入的 ZGC 垃圾回收器柄延,基本可用做到全階段并發(fā)標(biāo)記和回收蚀浆,值得期待缀程。

2)合理的堆內(nèi)存大小設(shè)置。堆大小不要設(shè)置過大市俊,建議不要超過系統(tǒng)內(nèi)存的 75%杨凑,避免出現(xiàn)系統(tǒng)內(nèi)存耗盡。最大堆大小和初始化堆的大小保持一致摆昧,避免堆震蕩撩满。新生代的大小設(shè)置比較關(guān)鍵,我們調(diào)整 GC 的頻率和耗時绅你,很多時候就是在調(diào)整新生代的大小伺帘,包括新生代和老年代的占比、新生代中 Eden 區(qū)和 Survivor 區(qū)的比例等忌锯,這些比例的設(shè)置還需要考慮各代中對象的晉升年齡伪嫁,整個過程需要考慮的東西還是比較多的。如果使用 G1 垃圾回收器汉规,新生代大小這一塊需要考慮的東西就少很多了礼殊,自適應(yīng)的策略會決定每一次的回收集合(CSet)。新生代的調(diào)整是 GC 調(diào)優(yōu)的核心针史,非常依賴經(jīng)驗晶伦,但是一般來說,Young GC 頻率高啄枕,意味著新生代太谢榕恪(或 Eden 區(qū)和 Survivor 配置不合理),Young GC 時間長频祝,意味著新生代過大泌参,這兩個方向大體不差。

3)降低 Full GC 的頻率常空。如果出現(xiàn)了頻繁的 Full GC 或者 老年代 GC沽一,很有可能是存在內(nèi)存泄漏,導(dǎo)致對象被長期持有漓糙,通過 dump 內(nèi)存快照進行分析铣缠,一般能較快地定位問題。除此之外昆禽,新生代和老年代的比例不合適蝗蛙,導(dǎo)致對象頻頻被直接分配到老年代,也有可能會造成 Full GC醉鳖,這個時候需要結(jié)合業(yè)務(wù)代碼和內(nèi)存快照綜合分析捡硅。此外,通過配置 GC 參數(shù)盗棵,可以幫助我們獲取很多 GC 調(diào)優(yōu)所需的關(guān)鍵信息壮韭,如配置-XX:+PrintGCApplicationStoppedTime-XX:+PrintSafepointStatistics-XX:+PrintTenuringDistribution北发,分別可以獲取 GC Pause 分布、安全點耗時統(tǒng)計泰涂、對象晉升年齡分布的信息鲫竞,加上 -XX:+PrintFlagsFinal 可以讓我們了解最終生效的 GC 參數(shù)等。

磁盤I/O和網(wǎng)絡(luò)I/O

4.1 磁盤 I/O 問題排查思路:

1.使用工具輸出磁盤相關(guān)的輸出的指標(biāo)逼蒙,常用的有 %wa(iowait)从绘、%util,根據(jù)輸判斷磁盤 I/O 是否存在異常是牢,譬如 %util 這個指標(biāo)較高僵井,說明有較重的 I/O 行為;

2.使用 pidstat 定位到具體進程驳棱,關(guān)注下讀或?qū)懙臄?shù)據(jù)大小和速率批什;

3.使用 lsof + 進程號,可查看該異常進程打開的文件列表(含目錄社搅、塊設(shè)備驻债、動態(tài)庫、網(wǎng)絡(luò)套接字等)形葬,結(jié)合業(yè)務(wù)代碼合呐,一般可定位到 I/O 的來源,如果需要具體分析笙以,還可以使用 perf 等工具進行 trace 定位 I/O 源頭淌实。

需要注意的是,%wa(iowait)的升高不代表一定意味著磁盤 I/O 存在瓶頸猖腕,這是數(shù)值代表 CPU 上 I/O 操作的時間占用的百分比拆祈,如果應(yīng)用進程的在這段時間內(nèi)的主要活動就是 I/O,那么也是正常的倘感。

4.2 網(wǎng)絡(luò) I/O 存在瓶頸放坏,可能的原因如下:

1.一次傳輸?shù)膶ο筮^大,可能會導(dǎo)致請求響應(yīng)慢老玛,同時 GC 頻繁轻姿;

2.網(wǎng)絡(luò) I/O 模型選擇不合理,導(dǎo)致應(yīng)用整體 QPS 較低逻炊,響應(yīng)時間長;

3.RPC 調(diào)用的線程池設(shè)置不合理犁享∮嗨兀可使用 jstack 統(tǒng)計線程數(shù)的分布,如果處于 TIMED_WAITING 或 WAITING 狀態(tài)的線程較多炊昆,則需要重點關(guān)注桨吊。舉例:數(shù)據(jù)庫連接池不夠用威根,體現(xiàn)在線程棧上就是很多線程在競爭一把連接池的鎖;

4.RPC 調(diào)用超時時間設(shè)置不合理视乐,造成請求失敗較多洛搀;

Java 應(yīng)用的線程堆棧快照非常有用佑淀,除了上面提到的用于排查線程池配置不合理的問題留美,其他的一些場景,如 CPU 飆高伸刃、應(yīng)用響應(yīng)較慢等谎砾,都可以先從線程堆棧入手。

5. 有用的一行命令

這一小節(jié)給出若干在定位性能問題的命令捧颅,用于快速定位景图。

1)查看系統(tǒng)當(dāng)前網(wǎng)絡(luò)連接數(shù)

netstat -n | awk'/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

2)查看堆內(nèi)對象的分布 Top 50(定位內(nèi)存泄漏)

jmap –histo:live $pid | sort-n -r -k2 | head-n50

3)按照 CPU/內(nèi)存的使用情況列出前10 的進程

#內(nèi)存

ps axo %mem,pid,euser,cmd | sort -nr | head-10

#CPU

ps -aeo pcpu,user,pid,cmd | sort -nr | head-10

4)顯示系統(tǒng)整體的 CPU利用率和閑置率

grep"cpu "/proc/stat | awk -F' ''{total = $2 + $3 + $4 + $5} END {print "idle \t used\n" $5*100/total "% " $2*100/total "%"}'

5)按線程狀態(tài)統(tǒng)計線程數(shù)(加強版)

jstack $pid | grep java.lang.Thread.State:|sort|uniq -c | awk'{sum+=$1; split($0,a,":");gsub(/^[ \t]+|[ \t]+$/, "", a[2]);printf "%s: %s\n", a[2], $1}; END {printf "TOTAL: %s",sum}';

6)查看最消耗 CPU 的 Top10 線程機器堆棧信息

推薦大家使用 show-busy-java-threads 腳本,該腳本可用于快速排查 Java 的 CPU 性能問題(top us值過高)碉哑,自動查出運行的 Java 進程中消耗 CPU 多的線程挚币,并打印出其線程棧,從而確定導(dǎo)致性能問題的方法調(diào)用扣典,該腳本已經(jīng)用于阿里線上運維環(huán)境妆毕。鏈接地址:https://github.com/oldratlee/useful-scripts/

7)火焰圖生成(需要安裝 perf、perf-map-agent激捏、FlameGraph 這三個項目):

#1.收集應(yīng)用運行時的堆棧和符號表信息(采樣時間30秒设塔,每秒99個事件);

sudo perf record -F99-p $pid -g -- sleep30; ./jmaps#

2.使用 perf script 生成分析結(jié)果远舅,生成的 flamegraph.svg 文件就是火焰圖闰蛔。

sudo perf script | ./pkgsplit-perf.pl | grep java | ./flamegraph.pl > flamegraph.svg

8)按照 Swap 分區(qū)的使用情況列出前 10 的進程

forfilein/proc/*/status ;?

do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file;?

done | sort -k 3 -n -r | head -10

9)JVM 內(nèi)存使用及垃圾回收狀態(tài)統(tǒng)計

#顯示最后一次或當(dāng)前正在發(fā)生的垃圾收集的誘發(fā)原因

jstat -gccause $pid

#顯示各個代的容量及使用情況

jstat -gccapacity $pid

#顯示新生代容量及使用情況

jstat -gcnewcapacity $pid

#顯示老年代容量

jstat -gcoldcapacity $pid

#顯示垃圾收集信息(間隔1秒持續(xù)輸出)

jstat -gcutil $pid1000

10)其他的一些日常命令

# 快速殺死所有的 java 進程

ps aux | grep java | awk'{ print $2 }'| xargs kill-9

# 查找/目錄下占用磁盤空間最大的top10文件

find / -type f -print0 | xargs-0du -h | sort -rh | head -n10

總結(jié)

性能優(yōu)化是一個很大的領(lǐng)域,這里面的每一個小點图柏,都可以拓展為數(shù)十篇文章去闡述序六。對應(yīng)用進行性能優(yōu)化,除了上面介紹的之外蚤吹,還有前端優(yōu)化例诀、架構(gòu)優(yōu)化(分布式、緩存使用等)裁着、數(shù)據(jù)存儲優(yōu)化繁涂、代碼優(yōu)化(如設(shè)計模式優(yōu)化)等,限于篇幅所限二驰,在這里并未一一展開扔罪,本文的這些內(nèi)容,只是起一個拋磚引玉的作用桶雀。同時矿酵,本文的東西是我的一些經(jīng)驗和知識唬复,并不一定全對,希望大家指正和補充全肮。

性能優(yōu)化是一個綜合性的工作敞咧,需要不斷地去實踐,將工具學(xué)習(xí)辜腺、經(jīng)驗學(xué)習(xí)融合到實戰(zhàn)中去休建,不斷完善,形成一套屬于自己的調(diào)優(yōu)方法論哪自。

此外丰包,雖然性能優(yōu)化很重要,但是不要過早在優(yōu)化上投入太多精力(當(dāng)然完善的架構(gòu)設(shè)計和編碼是必要的)壤巷,過早優(yōu)化是萬惡之源邑彪。一方面,提前做的優(yōu)化工作胧华,可能會不適用快速變化的業(yè)務(wù)需求寄症,反倒給新需求、新功能起了阻礙的作用矩动;另一方面有巧,過早優(yōu)化使得應(yīng)用復(fù)雜性升高,降低了應(yīng)用的可維護性悲没。何時進行優(yōu)化篮迎、優(yōu)化到什么樣的程度,是一個需要多方權(quán)衡的命題示姿。

詳細(xì)源碼請查看原文:https://developer.aliyun.com/article/741607?utm_content=g_1000098464

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甜橱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子栈戳,更是在濱河造成了極大的恐慌岂傲,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件子檀,死亡現(xiàn)場離奇詭異镊掖,居然都是意外死亡,警方通過查閱死者的電腦和手機褂痰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門亩进,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缩歪,你說我怎么就攤上這事镐侯。” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵苟翻,是天一觀的道長。 經(jīng)常有香客問我骗污,道長崇猫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任需忿,我火速辦了婚禮诅炉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屋厘。我一直安慰自己涕烧,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布汗洒。 她就那樣靜靜地躺著议纯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溢谤。 梳的紋絲不亂的頭發(fā)上瞻凤,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音世杀,去河邊找鬼阀参。 笑死,一個胖子當(dāng)著我的面吹牛瞻坝,可吹牛的內(nèi)容都是我干的蛛壳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼所刀,長吁一口氣:“原來是場噩夢啊……” “哼衙荐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起勉痴,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤赫模,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蒸矛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瀑罗,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年雏掠,在試婚紗的時候發(fā)現(xiàn)自己被綠了斩祭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡乡话,死狀恐怖摧玫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤诬像,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布屋群,位于F島的核電站,受9級特大地震影響坏挠,放射性物質(zhì)發(fā)生泄漏芍躏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一降狠、第九天 我趴在偏房一處隱蔽的房頂上張望对竣。 院中可真熱鬧,春花似錦榜配、人聲如沸否纬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽临燃。三九已至,卻和暖如春壁拉,著一層夾襖步出監(jiān)牢的瞬間谬俄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工弃理, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留溃论,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓痘昌,卻偏偏與公主長得像钥勋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辆苔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344