有時(shí)候好好的程序放到生產(chǎn)服務(wù)器上一段時(shí)間后,就會發(fā)現(xiàn)服務(wù)器響應(yīng)緩慢,進(jìn)而進(jìn)一步發(fā)現(xiàn)是cpu過高,于是就慌了,造成cpu過高的原因很多,不過大多是由于資源吃緊造成,例如:sql執(zhí)行過慢,程序里存在死循環(huán),數(shù)據(jù)庫連接未釋放,網(wǎng)絡(luò)阻塞導(dǎo)致的第三方框架代碼出現(xiàn)死循環(huán),大量的操作導(dǎo)致死鎖等,遇到此類問題不必緊張,coder君手把手教你克服心理障礙。 案例:死循環(huán)造成CPU過高public class CpuTest { public static void main(String[] args) throws InterruptedException { loop(); endlessLoop(); } public static void endlessLoop() throws InterruptedException { while (true) { System.out.println('hello world! loop!'); } } public static void loop(){ for (int i = 0; i < 10000; i++) { System.out.println('hello world! endless loop!'); } } }
分析點(diǎn)擊抽樣器->CPU->查看CPU樣例,發(fā)現(xiàn)endlessLoop()方法最耗CPU(這里有2個(gè)方法 loop和endlessLoop) 
查看線程cpu耗時(shí),發(fā)現(xiàn)main線程最耗時(shí),點(diǎn)擊增量,可以從此刻觀察,cpu耗時(shí)的增長速率 
查看線程dump,主要觀察main線程,發(fā)現(xiàn)main線程當(dāng)前狀態(tài)下一直在執(zhí)行 CpuTest.endlessLoop(CpuTest.java:14) ,這里可以定位問題位置,同時(shí)細(xì)心的童鞋可以觀察看后面執(zhí)行System.out.println(“”);方法是要先加鎖的。 
截圖一段,我生產(chǎn)服務(wù)器(tomcat+springmvc)main線程的情況,其實(shí)只想說明web項(xiàng)目啟動的main方法在中間件里。 

補(bǔ)充VisualVm只能定位JVM的cpu情況,但是生產(chǎn)主機(jī)上不光是Java程序,這時(shí)我們要采取另外的方案。 1.看監(jiān)控數(shù)據(jù)是否正常,cpu,mem。 

CPU占用1.5左右(100-98.0id) 內(nèi)存占用50%(435/100*100%=43.5%) 阿里云監(jiān)控內(nèi)存大小轉(zhuǎn)成實(shí)際占用內(nèi)存大小,類似windows ,平均負(fù)載 0.1 差不多,其他幾個(gè)參數(shù),這里暫不介紹。 2.假設(shè)異常,找到異常的PID。 這里推薦 htop (清晰進(jìn)程,線程, 命令行 ,排序支持鼠標(biāo)雙擊,過濾,kill程序,標(biāo)記某個(gè)線程或者進(jìn)程,安裝apt-get install htop ) 
如果你沒有服務(wù)器上安裝軟件的權(quán)限的話,就老老實(shí)實(shí)用 top 。通過 top 命令(默認(rèn)3秒刷新,回車空格手動刷新, top -d 5 5秒刷新,也可以進(jìn)入top后輸入 d 設(shè)置刷新時(shí)間, top -p 4360 監(jiān)控指定進(jìn)程),然后按X ,默認(rèn)按照CPU%排序,查看系統(tǒng)運(yùn)行情況,如果想強(qiáng)制按CPU 降序,則輸入大寫P,如果強(qiáng)制按內(nèi)存降序,則輸入大寫M(top命令是交互式的)。 
解讀: 1).現(xiàn)在系統(tǒng)時(shí)間 10:18.44 ,系統(tǒng)一直運(yùn)行了 131天16小時(shí)51分,當(dāng)前有1個(gè)用戶登錄系統(tǒng)(相同賬號也算不同用戶),平均負(fù)載分別為0.00,0.01,0.05(分別為1分鐘,5分鐘,15分鐘的負(fù)載情況,load average是每隔5秒鐘檢查一次活躍的進(jìn)程數(shù),用特定的算法得到的數(shù)值,然后除以邏輯CPU數(shù)量,如果負(fù)載持續(xù)大于cpu個(gè)數(shù),則表明負(fù)載過高) 。大概可以看出系統(tǒng)負(fù)載很低,運(yùn)行狀態(tài)健康。 2).當(dāng)前一共有103個(gè) 進(jìn)程 ,處于運(yùn)行的有2個(gè),處于休眠狀態(tài)的有101個(gè),處于停止?fàn)顟B(tài)的有0個(gè),處于僵尸狀態(tài)的有0個(gè)。大概可以看出系統(tǒng)進(jìn)程總數(shù)較少,環(huán)境比較單純,運(yùn)行中的進(jìn)程不多。 3). 0.3 us 用戶空間占用CPU 0.3%, 0.7 sy 內(nèi)核占用CPU 0.7%, 0.0 ni 改變過優(yōu)先級的進(jìn)程占用CPU的百分比, 98.0 id 空閑CPU的百分比為98.0 , 0.3 wa IO等待所占用CPU的百分比為0.3, 0.3 hi 硬中斷(Hardware IRQ)占用CPU的百分比為0.3(外設(shè)給CPU的異步信號(中斷),例如:網(wǎng)卡收到數(shù)據(jù)包), 0.0 si 軟中斷占用cpu的百分比為0(軟件本身給操作系統(tǒng)內(nèi)核的中斷信號,通常由硬中斷處理程序?qū)Σ僮飨到y(tǒng)內(nèi)核的中斷), 0.3 st 虛擬機(jī)被hypervisor偷去CPU的時(shí)間。 4). KiB Mem 代表內(nèi)存占用, 1016272 total 內(nèi)存總的大小1g(以kb為單位), 941492 used 使用中的內(nèi)存總量為0.9g, 74780 free 空閑內(nèi)存總量為74m(嚇一跳吧,才74M,這個(gè)不是實(shí)際剩余的內(nèi)內(nèi)存大小) , 115900 buffers 緩存的內(nèi)存量為115m, 389836 cached cached大小380M??臻e內(nèi)存總量只有74m,如果是windows去理解的話,此臺服務(wù)器已經(jīng)快掛了,實(shí)際內(nèi)存大小等于74M+ buffers+cached = 580m(哈哈,夠用,才占用一半呢), linux的內(nèi)存管理和windows是不一樣的,Linux會借用空閑的內(nèi)存當(dāng)作磁盤緩存, 磁盤數(shù)據(jù)緩存會讓linux運(yùn)行的更快,它永遠(yuǎn)不會從程序中拿出內(nèi)存,它沒有任何缺點(diǎn),只是會混淆新手,如果你的應(yīng)用程序需要更多的內(nèi)存,他們會回收一部分用作磁盤數(shù)據(jù)緩存的物理內(nèi)存,返回給應(yīng)用程序,這個(gè)過程不需要啟動交換,磁盤緩存(Disk caching)是不能禁用的, 但是可以釋放磁盤緩存 1).只釋放pagecache(文件緩存) echo 1 > /proc/sys/vm/drop_caches 2).釋放dentries和inodes echo 2 > /proc/sys/vm/drop_caches 3).釋放pagecache,dentries和inodes echo 3 > /proc/sys/vm/drop_caches 具體原理可以查看 linuxatemyram 我們利用 free 進(jìn)一步證實(shí)上面的內(nèi)容 1).shared代表被線程共享的內(nèi)存大小(大多數(shù)已經(jīng)舍棄掉),buffer用來給塊設(shè)備做緩存(記錄文件系統(tǒng)的metadata和tracking in flight pages),cache緩存文件(第二次打開就很快),linux系統(tǒng)cached比較大,cached大小390m,buffers為110m。
2).435288代表 -buffers/cache ( 應(yīng)用程序?qū)嶋H使用330m內(nèi)存 ) 580984 代表 +buffers/cache( 實(shí)際剩余內(nèi)存大小580m )。 3).重要等式 total = used + free used(-buffers/cache) = used(Mem)-buffers(Mem)-cached(Mem) free(+buffers/cache) = free(Mem) + buffers(Mem) +cached(Mem) 。 4). free 命令的值是從 /proc/meminfo 文件里讀到的。 5).Swap 交換區(qū) 總共大小 0 kb,已經(jīng)使用0kb,釋放了0Kb, 如果swap used > 0,則可以說明系統(tǒng)內(nèi)存瓶頸了 。 6).內(nèi)存總和 free -t 總和等于total(Mem) + total(Swap)。 7).一般常用命令 free -s 3 每3秒觀察一次內(nèi)存使用情況。 占用cpu最高的進(jìn)程是java,PID為28628,USER進(jìn)程所有者root,PR優(yōu)先級20,NI為0(負(fù)值優(yōu)先級高,正值優(yōu)先級低,PR=NI+20) ,VIRT進(jìn)程使用虛擬內(nèi)存1G(java進(jìn)程最高只能占用到1G),RES進(jìn)程實(shí)際使用的物理內(nèi)存270M(不包含swap和shared),SHR共享內(nèi)存大小5M,S進(jìn)程狀態(tài)隨眠狀態(tài)(S睡眠,R運(yùn)行,T停止或被跟蹤,Z僵尸,D不可中斷睡眠態(tài)),%CPU占用CPU 0.7%,%MEM占用內(nèi)存27%,TIME+進(jìn)程使用cpu的時(shí)間54分40.12秒??傮w看java進(jìn)程運(yùn)行良好。 3.這里假設(shè)PID為16368的 進(jìn)程 占用CPU比較高(因?yàn)樽隽硕啻螌?shí)驗(yàn),所以PID沒辦法和上面的28628保持一致), 先用 ps -ef | grep java ,也可以用 htop filter java,也可以 jps -v 找到 java的進(jìn)程ID 16368(只能查看當(dāng)前用戶的java pid,不太建議使用,-v顯示詳細(xì)信息,也可以不加) 

?代表終端設(shè)備未知,Sl代表休眠狀態(tài)多線程 Linux通過進(jìn)程查看線程的方法 1). htop 按t(顯示進(jìn)程線程嵌套關(guān)系)和H(顯示線程) ,然后F4過濾進(jìn)程名。2). ps -eLf | grep java (快照,帶線程命令,e是顯示全部進(jìn)程,L是顯示線程,f全格式輸出) 3). pstree -p <pid> (顯示進(jìn)程樹,不加pid顯示所有) 4). top -Hp <pid> (實(shí)時(shí)) 5). ps -T -p <pid> (快照) 推薦程度按數(shù)字從小到大 。 
4.利用 jstack 16368 > 2016-1-21.tdump (jstack是jdk自帶的生成java stack和native stack的工具) 把threaddump輸出到文件里,這里假設(shè)PID為16369(一般比進(jìn)程號+1的是main線程,如果存在main線程貌似最先分配)的線程占用CPU最高,將其ID轉(zhuǎn)成16進(jìn)制0x3ff1(使用命令 printf '%x/n' 16369 輸出3ff1)。 5.搜索threaddump文件,nid= 0x3ff1的線程堆棧,通過堆棧信息,就可以定位到占用CPU最高的代碼地方(這里是正常的)。 
好啦,CPU沖高問題排查完畢,有了這樣的知識體系,以后完全可以舉一反三,coder君同時(shí)附上其他常用配合命令。 每隔一秒顯示新生代,老年代,持久代垃圾回收情況 jstat -gcutil 16368 1000ms 參考 jstat -help ,也可以查看遠(yuǎn)程主機(jī)的GC情況,需要遠(yuǎn)程主機(jī)開啟jstatd服務(wù)。
觀察堆內(nèi)存情況 jmap -dump:format=b,file=2016-1-21.mdump 16368 生成堆dump,放到mat或者vvm進(jìn)行分析,上篇分析OOM有講到。
查看jvm系統(tǒng)參數(shù) jinfo 16368
【深入淺出-VisualVM】(1):遠(yuǎn)程調(diào)試【深入淺出-VisualVM】(2):分析堆內(nèi)存OOM【深入淺出-VisualVM】(3):分析PermGenOOM 原文 http://mousycoder.com/2016/02/14/thinking-in-visualvm/thinking-in-visualvm-4/
|