背景介紹
我負(fù)責(zé)一個(gè)物聯(lián)網(wǎng)平臺的開發(fā)與運(yùn)維工作八堡,2020年12月18日的一天兄渺,恰逢周五溶耘,本來以為可以劃劃水就可以過愉快的周末了凳兵,一大早讓我看下昨天的設(shè)備上報(bào)的數(shù)據(jù)情況庐扫,打開瀏覽器輸入網(wǎng)址,無法正常登錄萨醒,提示服務(wù)器內(nèi)部錯(cuò)誤富纸!
我心里一想不對呀,本人照看的系統(tǒng)已經(jīng)穩(wěn)定運(yùn)行很長一段時(shí)間了堵漱,怎么會這樣勤庐?都坐下愉镰,不要慌丈探,我們慢慢的來處理。
處理過程
首先辨宠,通過我的FinalShell工具通過ssh登錄服務(wù)器嗤形,一眼就看到了讓我震驚的結(jié)果赋兵,我開始慌了。历造。吭产。
有個(gè)java進(jìn)程居然內(nèi)存和CPU占用都這么高臣淤,然后我簡單看了一下進(jìn)程號邑蒋,定位到是物聯(lián)網(wǎng)平臺的后臺服務(wù)進(jìn)程(由于該平臺是采用docker部署的,因此需要進(jìn)入docker容器內(nèi)部進(jìn)行查看)
docker exec -it cfe1916c608a /bin/bash
通過docker exec進(jìn)入容器內(nèi)部寺董,首先還是使用我們比較常用top看下情況
果然覆糟,與我們看到的現(xiàn)象一致,有個(gè)java進(jìn)程RES占有高達(dá)8.6g遮咖,%CPU高達(dá)277.0滩字,這時(shí)突然有些興奮呀
,我又可以躺坑了御吞,事后會被作為經(jīng)驗(yàn)文章分享出來麦箍,也就是你現(xiàn)在看到的樣子??。然后我們一步步來分析和處理陶珠。。。
接下來,我們主要從CPU升高的方向先入手分析。復(fù)制該進(jìn)程的id號8,通過執(zhí)行top -Hp查看當(dāng)前進(jìn)程的各個(gè)線程執(zhí)行情況
top -Hp 8
可以看到委刘,前面幾個(gè)線程的CPU占用都非常高漆际,我們隨機(jī)挑選一個(gè)PID為13的線程進(jìn)行分析,需要注意的是,在jstack命令展示中,線程id都是轉(zhuǎn)化成的十六進(jìn)制形式,使用以下命令打印線程id的16進(jìn)制結(jié)果
重點(diǎn)來了
蜒车,執(zhí)行jstack查看堆棧信息情況
jstack 8 |grep d
終于發(fā)現(xiàn)因?yàn)槭裁磳?dǎo)致CPU升高了叛氨,可以看到a,b饭冬,c,d都是代表GC線程(PS:上圖中的10~13這幾個(gè)都是GC線程)冰沙,我們大概猜測應(yīng)該就是因?yàn)镚C進(jìn)行頻繁的垃圾回收侥啤,導(dǎo)致其他業(yè)務(wù)無法正常的工作糠雨。好的,我們還是通過jstat驗(yàn)證一下
jstat -gcutil 8 2000 10
果然邮府,這個(gè)FGC與FGCT顯示的fullGC的次數(shù)和時(shí)間是不是讓你頭皮發(fā)麻 ??。然后,給大家一個(gè)小技巧,我們先通過jmap -histo命令先大致查看下堆內(nèi)存的對象數(shù)量和大小情況
jmap -histo:live 8 | more
從分析中我們可以看到【B與【C占有都非常高怜奖,這是什么玩意讹语?其實(shí)這個(gè)Byte和Char數(shù)組才菠,大膽猜測可能是有大量的String字符串渠牲。接奈。。
class name是對象類型遂填,說明如下:
- B byte
- C char
- D double
- F float
- I int
- J long
- Z boolean
- [ 數(shù)組客税,如[I表示int[]
- [L+類名 其他對象
我們還是來好好分析下目胡,到底是什么原因?qū)е碌腉C經(jīng)過這么努力在釋放堆內(nèi)存還是釋放不出來链快,幫幫JVM虛擬機(jī)診斷下病因筑累,這個(gè)時(shí)候其實(shí)大家應(yīng)該已經(jīng)知道我們應(yīng)該看下堆內(nèi)存里面到底是什么
首先,我們通過jmap -dump導(dǎo)出現(xiàn)在JVM的堆內(nèi)存日志情況
jmap -dump:format=b,file=dump.dat 8
然后茵典,從服務(wù)器下載dump.dat文件到本機(jī),通過Eclipse Memory Analyzer工具對其進(jìn)行分析查看
內(nèi)存使用整體情況
直接點(diǎn)擊下方Reports→Leak Suspects鏈接來生成報(bào)告煮盼,查看導(dǎo)致內(nèi)存泄露的罪魁禍?zhǔn)?/p>
從圖中可以看出一個(gè)可疑對象消耗了近93.43%的內(nèi)存。接著往下看报破,點(diǎn)擊Details查看詳細(xì)情況
備注:
Shallow Heap 為對象自身占用的內(nèi)存大小悠就,不包括它引用的對象。
Retained Heap 為當(dāng)前對象大小 + 當(dāng)前對象可直接或間接引用到的對象的大小總和
點(diǎn)擊dominator_tree查看entries heap對象引用關(guān)系tree進(jìn)行分析
最終通過點(diǎn)擊entries引用tree層層查看對象的引用情況泛烙,查看熟悉的東西理卑,定位到好像與device_log_mtu_2020-12
這個(gè)地方有關(guān)系
其實(shí)我們已經(jīng)大概定位到問題出現(xiàn)位置了翘紊,接下來其實(shí)就要結(jié)合業(yè)務(wù)系統(tǒng)情況來追查業(yè)務(wù)情況與分析系統(tǒng)代碼蔽氨,最終定位到問題的原因:
以下截圖是我與系統(tǒng)的技術(shù)開發(fā)人員的交流,最終大概定位到問題的原因:因?yàn)榇a的邏輯bug導(dǎo)致出現(xiàn)死循環(huán)
總結(jié)
先通過top命令查看CPU與內(nèi)存情況,查看是什么進(jìn)程的CPU和內(nèi)存飆高鹉究,如果是CPU比較高宇立,則可以通過top -Hp <pid>命令查看當(dāng)前進(jìn)程的各個(gè)線程運(yùn)行情況,找出CPU過高的線程以后自赔,將其線程id轉(zhuǎn)換為十六進(jìn)制的形式后妈嘹,然后通過jstack日志查看改線程的工作狀態(tài)和調(diào)用棧情況,這里有兩種情況:
- 如果是正常的用戶線程绍妨,則通過該線程的堆棧信息查看其具體在哪出代碼運(yùn)行比較消耗CPU;
- 如果該線程是
VM Thread
润脸,則通過jstat -gcutil <pid> <period> <times>
命令監(jiān)控當(dāng)前系統(tǒng)的GC狀況,然后通過jmap dump:format=b,file=<filepath> <pid>
導(dǎo)出系統(tǒng)當(dāng)前的內(nèi)存數(shù)據(jù)他去。導(dǎo)出之后將內(nèi)存情況放到eclipse的mat工具中進(jìn)行分析即可得出內(nèi)存中主要是什么對象比較消耗內(nèi)存毙驯,進(jìn)而可以處理相關(guān)代碼
如果企業(yè)內(nèi)部本身沒有相關(guān)監(jiān)控與運(yùn)維工具,那我們可以使用JDK本身提供了一些監(jiān)控分析工具(jps灾测、jstack爆价、jmap、jhat媳搪、jstat铭段、hprof等),通過靈活運(yùn)用這些工具幾乎能夠定位Java項(xiàng)目80%的性能問題秦爆。最后在這里推薦一個(gè)Alibaba開源的Java診斷工具Arthas可以幫助你解決:
- 這個(gè)類從哪個(gè) jar 包加載的序愚?為什么會報(bào)各種類相關(guān)的 Exception?
- 我改的代碼為什么沒有執(zhí)行到?難道是我沒 commit?分支搞錯(cuò)了辣辫?
- 遇到問題無法在線上 debug善绎,難道只能通過加日志再重新發(fā)布嗎?
- 線上遇到某個(gè)用戶的數(shù)據(jù)處理有問題驯绎,但線上同樣無法 debug,線下無法重現(xiàn)!
- 是否有一個(gè)全局視角來查看系統(tǒng)的運(yùn)行狀況埂软?
- 有什么辦法可以監(jiān)控到JVM的實(shí)時(shí)運(yùn)行狀態(tài)?
- 怎么快速定位應(yīng)用的熱點(diǎn)纫事,生成火焰圖勘畔?
參考資料
系統(tǒng)運(yùn)行緩慢,CPU 100%丽惶,以及Full GC次數(shù)過多問題的排查思路
JVM性能調(diào)優(yōu)監(jiān)控工具jps炫七、jstack、jmap钾唬、jhat万哪、jstat侠驯、hprof_憧憬的專欄-CSDN博客
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!