如何排查java應(yīng)用中CPU使用率高或內(nèi)存占用高的問題

如何排查java應(yīng)用中CPU使用率高或內(nèi)存占用高的問題?這類問題的排查步驟基本通用的。現(xiàn)在通過一個(gè)具體的例子來說明。

問題描述

最近有個(gè)線上項(xiàng)目每天0點(diǎn)過后CPU使用率會(huì)上升至200%到300%。

排查過程

本節(jié)內(nèi)容是對(duì)排查過程的復(fù)盤,過程記錄會(huì)比較詳細(xì)。如果想知道具體的命令操作,可以直接看總結(jié)部分內(nèi)容。

1)當(dāng)CPU再次暴漲的時(shí)候,首先我們可以通過top -c查看CPU使用率高的進(jìn)程的PID。

2)然后使用top -p PID -H查看CPU使用率高的線程信息。如果CPU使用率高的線程是比較固定的,那么我們記下對(duì)應(yīng)線程的PID。
執(zhí)行top -p 14639 -H得出下圖結(jié)果:


記下4個(gè)線程的PID: 14643、14644、14641、14642

3)接下來通過jstack PID > xxx.log輸出java應(yīng)用當(dāng)前堆棧信息到文件。

4)第2步中,我們記下了CPU使用率高的線程PID,現(xiàn)在將4個(gè)線程的PID轉(zhuǎn)成16進(jìn)制: 3933、3934、3931、3932。接著在jstack輸出的堆棧文件里,搜索nid等于3933、3934、3931、3932的線程信息。如下圖:


從圖中可以看出,對(duì)應(yīng)的是GC線程。GC消耗大,那就有可能是由于內(nèi)存不足,頻繁執(zhí)行Full GC導(dǎo)致的。
再使用jstat -gc PID查看jvm的GC情況,連續(xù)執(zhí)行4次jstat -gc 14639命令,發(fā)現(xiàn)FGC的數(shù)值變化比較快。這就說明Full GC確實(shí)執(zhí)行很頻繁。如下圖:

5)從第1步的截圖中,可以看到CPU高的時(shí)候整個(gè)項(xiàng)目的內(nèi)存占用1.3G左右。既然是內(nèi)存問題,那么就需要使用jmap -histo:live PID > xxx.log分析下jvm內(nèi)存存活對(duì)象的統(tǒng)計(jì)情況。如下圖:


從圖中可以看出,byte對(duì)象([B)內(nèi)存占用特別高,而且出現(xiàn)了一個(gè)具體的類:ByteArrayRow。這是一個(gè)jdbc做查詢時(shí)候封裝數(shù)據(jù)用的一個(gè)類,這個(gè)類里包含有byte數(shù)組。通過這個(gè)統(tǒng)計(jì)結(jié)果初步懷疑是做數(shù)據(jù)庫查詢時(shí)候,查詢了太多內(nèi)容到了內(nèi)存,導(dǎo)致了內(nèi)存不足。由于統(tǒng)計(jì)中沒有出現(xiàn)具體的業(yè)務(wù)類,所以就以為只是請(qǐng)求量比較大,導(dǎo)致的內(nèi)存消耗過大。當(dāng)時(shí)暫時(shí)將jvm的堆內(nèi)存增大到2G。

6)應(yīng)用jvm堆內(nèi)存調(diào)大之后,到了0點(diǎn)還是出現(xiàn)了CPU高漲的問題。


內(nèi)存占用了2G多,按照目前項(xiàng)目的請(qǐng)求量來說,2G內(nèi)存不可能被占滿了,所以說明并不是請(qǐng)求量大導(dǎo)致的結(jié)果,而是由于某塊代碼查詢數(shù)據(jù)量過大導(dǎo)致的問題。

7)再次運(yùn)行jmap -histo:live PID > xxx.log將內(nèi)存對(duì)象統(tǒng)計(jì)情況輸出到文件。結(jié)果如下圖:


這次的輸出結(jié)果出現(xiàn)了業(yè)務(wù)類MiniProgram_User_Info,那就可以針對(duì)這個(gè)業(yè)務(wù)類去排查異常代碼的位置了。不過,除非比較清楚這個(gè)類具體使用的地方,否則即使出現(xiàn)了具體的類名還是比較難定位異常代碼的位置。
這時(shí)候,我們可以使用jmap -dump:live,format=b,file=xxx.hprof PID命令來輸出內(nèi)存對(duì)象的明細(xì),來定位具體方法位置。這個(gè)命令是將內(nèi)存里的所有信息都輸出出來,輸出的文件大小和內(nèi)存大小基本一致。而且這個(gè)命令會(huì)導(dǎo)致應(yīng)用暫時(shí)掛起,所以謹(jǐn)慎使用。

8)這次將內(nèi)存明細(xì)輸出之后,dump文件大小為2G。用jdk自帶的jhat命令可以分析。之前分析其他dump文件用jhat還是比較方便的。不過,分析這次的dump文件,給了10G運(yùn)行內(nèi)存給jhat命令才勉強(qiáng)打開了文件:jhat -J-mx10G -port 7170。而且內(nèi)存對(duì)象比較多,查找問題不方便。最后找到了一款神器: jprofiler。用jprofier分析dump文件需要的運(yùn)行內(nèi)存比較少,而且問題定位很方便。很快就定位出了內(nèi)存中的大對(duì)象,占用了1G多內(nèi)存的對(duì)象:


大對(duì)象對(duì)應(yīng)的線程堆棧:


如上圖,至此問題已經(jīng)定位完成了。最后排查代碼,最終發(fā)現(xiàn)凌晨時(shí)候,會(huì)將數(shù)據(jù)庫里100多萬條數(shù)據(jù)查詢出來。內(nèi)存不足導(dǎo)致頻繁GC,結(jié)果就是CPU使用率暴漲。

總結(jié)

一、在排查問題的過程中針對(duì)CPU的問題,使用以下命令組合來排查問題
1、查看問題進(jìn)程,得到進(jìn)程PID:
top -c

2、查看進(jìn)程里的線程明細(xì),并手動(dòng)記下CPU異常的線程PID:
top -p PID -H

3、使用jdk提供jstack命令打印出項(xiàng)目堆棧:
jstack pid > xxx.log

線程PID轉(zhuǎn)成16進(jìn)制,與堆棧中的nid對(duì)應(yīng),定位問題代碼位置。

二、針對(duì)內(nèi)存問題,使用以下命令組合來排查問題:
1、查看內(nèi)存中的存活對(duì)象統(tǒng)計(jì),找出業(yè)務(wù)相關(guān)的類名:
jmap -histo:live PID > xxx.log

2、通過簡單的統(tǒng)計(jì)還是沒法定位問題的話,就輸出內(nèi)存明細(xì)來分析。這個(gè)命令會(huì)將內(nèi)存里的所有信息都輸出,輸出的文件大小和內(nèi)存大小基本一致。而且會(huì)導(dǎo)致應(yīng)用暫時(shí)掛起,所以謹(jǐn)慎使用。
jmap -dump:live,format=b,file=xxx.hprof PID

3、 最后對(duì)dump出來的文件進(jìn)行分析。文件大小不是很大的話,使用jdk自帶的jhat命令即可:
jhat -J-mx2G -port 7170

4、dump文件太大的話,可以使用jprofiler工具來分析。jprofiler工具的使用,這里不做詳細(xì)介紹,有興趣可以搜索一下。

三、需要分析GC情況,可以使用以下命令:
jstat -gc PID

這里簡單介紹一下java8里面這個(gè)命令得出的列表各個(gè)列的含義:

S0C:第一個(gè)幸存區(qū)的大小
S1C:第二個(gè)幸存區(qū)的大小
S0U:第一個(gè)幸存區(qū)的使用大小
S1U:第二個(gè)幸存區(qū)的使用大小
EC:伊甸園區(qū)的大小
EU:伊甸園區(qū)的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法區(qū)大小
MU:方法區(qū)使用大小
CCSC:壓縮類空間大小
CCSU:壓縮類空間使用大小
YGC:年輕代垃圾回收次數(shù)
YGCT:年輕代垃圾回收消耗時(shí)間
FGC:老年代垃圾回收次數(shù)
FGCT:老年代垃圾回收消耗時(shí)間
GCT:垃圾回收消耗總時(shí)間

一般會(huì)比較關(guān)注YGC和FGC的次數(shù)。

內(nèi)容補(bǔ)充

1、jstack輸出的堆棧文件可以上傳到下面這個(gè)網(wǎng)站,這個(gè)網(wǎng)站可以對(duì)堆棧內(nèi)容進(jìn)行統(tǒng)計(jì)匯總,方便我們做分析:http://fastthread.io/index.jsp

2、排查過程小節(jié)中的第5步,jmap命令執(zhí)行完后沒有輸出業(yè)務(wù)類,而第7步在卻有。這個(gè)是因?yàn)榈?步操作的時(shí)候只有1G多的內(nèi)存,代碼還沒執(zhí)行到業(yè)務(wù)對(duì)象的封裝,內(nèi)存就不夠了,后續(xù)的代碼無法被執(zhí)行到。第7步操作的時(shí)候內(nèi)存調(diào)整到2G,所以有部分業(yè)務(wù)對(duì)象已經(jīng)被創(chuàng)建了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 參考自 http://techblog.youdao.com/?p=961 在分析具體故障,先介紹一下幾種常用的工...
    抓兔子的貓閱讀 6,002評(píng)論 1 27
  • 注:最近一直想出一篇介紹JVM底層函數(shù)調(diào)用的博客,奈何越寫越多,現(xiàn)在還沒寫完,先來個(gè)簡單的安慰下我受傷的心靈 滴滴...
    miaoLoveCode閱讀 4,212評(píng)論 10 47
  • Java 應(yīng)用性能優(yōu)化是一個(gè)老生常談的話題,典型的性能問題如頁面響應(yīng)慢、接口超時(shí),服務(wù)器負(fù)載高、并發(fā)數(shù)低,數(shù)據(jù)庫頻...
    Rick617閱讀 7,373評(píng)論 1 9
  • 我們?cè)谑褂肬IImageView幀動(dòng)畫時(shí)會(huì)碰到加載到內(nèi)存的圖片不會(huì)自動(dòng)釋放,占用很多的內(nèi)存,這時(shí)我們可能使用 : ...
    xSirFting閱讀 3,100評(píng)論 2 3
  • 那時(shí)的你,在操場(chǎng)上奔跑。 曾以為,我也會(huì)有轟轟烈烈的愛情。我也曾以為,我們會(huì)不分開,我還想過十年之后,太美的承諾因...
    簡小帆閱讀 445評(píng)論 2 1