好好地碼代碼呢,突然接到線上的告警,說(shuō)是CPU飆高。然后就稀里嘩啦的連上線上的服務(wù)器,使用top命令查看了一下CPU情況,發(fā)現(xiàn)我們的服務(wù)CPU占到600%。然后就開(kāi)始定位CPU占用高的線程。主要有以下幾步:
查詢出問(wèn)題的服務(wù)進(jìn)程id : ps -ef | grep mrf-center | grep -v grep
查詢?cè)撨M(jìn)程內(nèi)最耗費(fèi)CPU的線程 top -Hp pid
轉(zhuǎn)換線程id為16進(jìn)制 printf "%x\n" 21742
查找特定線程的信息 jstack 21711 | grep 54ee
具體的定位過(guò)程可以參考大神你假笨的文章如何定位消耗CPU最多的線程
最后定位出這樣一個(gè)線程

好了,問(wèn)題來(lái)了,CPU占用過(guò)高不是因?yàn)闃I(yè)務(wù)代碼中有CPU密集型任務(wù)或者是死循環(huán)造成的,而是因?yàn)镚C太頻繁導(dǎo)致的。好,那就驗(yàn)證一下吧。

那gc情況來(lái)看可以看出以下幾個(gè)問(wèn)題
- 老年代和年輕代快占滿了
- 頻繁Full GC,但是回收效果不好
- 兩個(gè)Survivor區(qū)有時(shí)候都是空的
由此可以猜想頻繁Full GC是因?yàn)槔夏甏煺紳M了,而老年代慢了不是因?yàn)閑den區(qū)中的對(duì)象因?yàn)槟挲g到了可以晉升為老年代的年齡,而是因?yàn)镋den區(qū)中有大對(duì)象,導(dǎo)致在進(jìn)行minor gc后,無(wú)法回收的對(duì)象過(guò)大導(dǎo)致沒(méi)法放進(jìn)Survivor區(qū),從而只能放進(jìn)老年代。
所以用一句總結(jié)就是,大對(duì)象很可怕,朝生夕死的大對(duì)象更可怕
那接下來(lái)就需要驗(yàn)證自己的猜想,使用jmap命令來(lái)dump出jvm的內(nèi)存
jmap -dump:format=b,file=mm2.dump pid
注意,jmap命令會(huì)導(dǎo)致jvm停滯,線上慎用。
下面就是使用工具來(lái)分析一下堆內(nèi)存文件了,推薦使用MAT工具(eclipse 插件)。不過(guò)我不太推薦在eclipse中安裝這個(gè)插件,因?yàn)樵诜治龆褍?nèi)存的時(shí)候比較耗費(fèi)內(nèi)存,而eclipse又是比較耗費(fèi)內(nèi)存的,所以我推薦使用stand-alone的mat工具,可以在此下載
但是,不幸的是你可能打不開(kāi)這個(gè)工具,并且報(bào)下面這個(gè)錯(cuò)誤
Java was started but returned exit code=13
這個(gè)問(wèn)題可以參照下面這個(gè)來(lái)解決Java was started but returned exit code=13
還有就是,一般堆內(nèi)存文件都比較大,比較耗費(fèi)內(nèi)存。所以為了加載得比較快的話,可以調(diào)整一下文件
MemoryAnalyzer.ini中的最大堆內(nèi)存 。比如我就調(diào)整為2G了。-Xmx2048m
好了,完事具備,只欠分析堆內(nèi)存了。
查看histogram圖,然后按照retained heap 排序,可以看到這個(gè)PageData占用了將近600M的內(nèi)存。
為什么就看PageData類(lèi)型的對(duì)象呢,因?yàn)檫@個(gè)對(duì)象是我們系統(tǒng)中定義的對(duì)象,所以優(yōu)先看

再看一下支配樹(shù)

好了,發(fā)現(xiàn)是因?yàn)镻ageData中的List中裝了非常多的HashMap對(duì)象。由此可見(jiàn)是因?yàn)閺臄?shù)據(jù)庫(kù)中查詢了過(guò)多的對(duì)象導(dǎo)致的。
這樣就大概知道了是因?yàn)榇a中有一個(gè)不合理的查詢。下面即使定位是哪一個(gè)查詢了。
其實(shí)在看到支配樹(shù)的數(shù)據(jù)的時(shí)候也大概能知道是哪一個(gè)表中的數(shù)據(jù)了,再結(jié)合一下查詢就大概知道了。
不過(guò)我是從線程棧中看到的(可以通過(guò)使用jstack命令來(lái)dump出線程棧)

好了,至此就定位出問(wèn)題了。