“
處理過(guò)線上問(wèn)題的同學(xué)基本上都會(huì)遇到系統(tǒng)突然運(yùn)行緩慢,CPU 100%慨代,以及 Full GC 次數(shù)過(guò)多的問(wèn)題邢笙。
當(dāng)然啸如,這些問(wèn)題最終導(dǎo)致的直觀現(xiàn)象就是系統(tǒng)運(yùn)行緩慢侍匙,并且有大量的報(bào)警。
本文主要針對(duì)系統(tǒng)運(yùn)行緩慢這一問(wèn)題叮雳,提供該問(wèn)題的排查思路想暗,從而定位出問(wèn)題的代碼點(diǎn),進(jìn)而提供解決該問(wèn)題的思路帘不。
對(duì)于線上系統(tǒng)突然產(chǎn)生的運(yùn)行緩慢問(wèn)題说莫,如果該問(wèn)題導(dǎo)致線上系統(tǒng)不可用,那么首先需要做的就是寞焙,導(dǎo)出 jstack 和內(nèi)存信息储狭,然后重啟系統(tǒng),盡快保證系統(tǒng)的可用性捣郊。
這種情況可能的原因主要有兩種:
代碼中某個(gè)位置讀取數(shù)據(jù)量較大辽狈,導(dǎo)致系統(tǒng)內(nèi)存耗盡,從而導(dǎo)致 Full GC 次數(shù)過(guò)多呛牲,系統(tǒng)緩慢刮萌。
代碼中有比較耗 CPU 的操作,導(dǎo)致 CPU 過(guò)高娘扩,系統(tǒng)運(yùn)行緩慢着茸。
相對(duì)來(lái)說(shuō)壮锻,這是出現(xiàn)頻率最高的兩種線上問(wèn)題,而且它們會(huì)直接導(dǎo)致系統(tǒng)不可用涮阔。
另外有幾種情況也會(huì)導(dǎo)致某個(gè)功能運(yùn)行緩慢猜绣,但是不至于導(dǎo)致系統(tǒng)不可用:
代碼某個(gè)位置有阻塞性的操作,導(dǎo)致該功能調(diào)用整體比較耗時(shí)敬特,但出現(xiàn)是比較隨機(jī)的途事。
某個(gè)線程由于某種原因而進(jìn)入 WAITING 狀態(tài),此時(shí)該功能整體不可用擅羞,但是無(wú)法復(fù)現(xiàn)尸变。
由于鎖使用不當(dāng),導(dǎo)致多個(gè)線程進(jìn)入死鎖狀態(tài)减俏,從而導(dǎo)致系統(tǒng)整體比較緩慢召烂。
對(duì)于這三種情況,通過(guò)查看 CPU 和系統(tǒng)內(nèi)存情況是無(wú)法查看出具體問(wèn)題的娃承,因?yàn)樗鼈兿鄬?duì)來(lái)說(shuō)都是具有一定阻塞性操作奏夫,CPU 和系統(tǒng)內(nèi)存使用情況都不高,但是功能卻很慢历筝。
下面我們就通過(guò)查看系統(tǒng)日志來(lái)一步一步甄別上述幾種問(wèn)題酗昼。
Full GC 次數(shù)過(guò)多
相對(duì)來(lái)說(shuō),這種情況是最容易出現(xiàn)的梳猪,尤其是新功能上線時(shí)麻削。
對(duì)于 Full GC 較多的情況,其主要有如下兩個(gè)特征:
線上多個(gè)線程的 CPU 都超過(guò)了 100%春弥,通過(guò) jstack 命令可以看到這些線程主要是垃圾回收線程呛哟。
通過(guò) jstat 命令監(jiān)控 GC 情況,可以看到 Full GC 次數(shù)非常多匿沛,并且次數(shù)在不斷增加扫责。
首先我們可以使用 top 命令查看系統(tǒng) CPU 的占用情況,如下是系統(tǒng) CPU 較高的一個(gè)示例:
top - 08:31:10 up 30 min,? 0 users,? load average: 0.73, 0.58, 0.34
KiB Mem:? 2046460 total,? 1923864 used,? 122596 free,? ? 14388 buffers
KiB Swap:? 1048572 total,? ? ? ? 0 used,? 1048572 free.? 1192352 cached Mem
? PID USER? ? ? PR? NI? ? VIRT? ? RES? ? SHR S? %CPU %MEM? ? TIME+ COMMAND
? ? 9 root? ? ? 20? 0 2557160 288976? 15812 S? 98.0 14.1? 0:42.60 java
可以看到逃呼,有一個(gè) Java 程序此時(shí) CPU 占用量達(dá)到了 98.8%鳖孤,此時(shí)我們可以復(fù)制該進(jìn)程 id9,并且使用如下命令查看該進(jìn)程的各個(gè)線程運(yùn)行情況:
top -Hp 9
該進(jìn)程下的各個(gè)線程運(yùn)行情況如下:
top - 08:31:16 up 30 min,? 0 users,? load average: 0.75, 0.59, 0.35
Threads:? 11 total,? 1 running,? 10 sleeping,? 0 stopped,? 0 zombie
%Cpu(s):? 3.5 us,? 0.6 sy,? 0.0 ni, 95.9 id,? 0.0 wa,? 0.0 hi,? 0.0 si,? 0.0 st
KiB Mem:? 2046460 total,? 1924856 used,? 121604 free,? ? 14396 buffers
KiB Swap:? 1048572 total,? ? ? ? 0 used,? 1048572 free.? 1192532 cached Mem
? PID USER? ? ? PR? NI? ? VIRT? ? RES? ? SHR S %CPU %MEM? ? TIME+ COMMAND
? 10 root? ? ? 20? 0 2557160 289824? 15872 R 79.3 14.2? 0:41.49 java
? 11 root? ? ? 20? 0 2557160 289824? 15872 S 13.2 14.2? 0:06.78 java
可以看到抡笼,在進(jìn)程為 9 的 Java 程序中各個(gè)線程的 CPU 占用情況苏揣,接下來(lái)我們可以通過(guò) jstack 命令查看線程 id 為 10 的線程為什么耗費(fèi) CPU 最高。
需要注意的是蔫缸,在 jsatck 命令展示的結(jié)果中腿准,線程 id 都轉(zhuǎn)換成了十六進(jìn)制形式。
可以用如下命令查看轉(zhuǎn)換結(jié)果,也可以找一個(gè)科學(xué)計(jì)算器進(jìn)行轉(zhuǎn)換:
root@a39de7e7934b:/# printf "%x\n" 10
a
這里打印結(jié)果說(shuō)明該線程在 jstack 中的展現(xiàn)形式為 0xa吐葱,通過(guò) jstack 命令我們可以看到如下信息:
"main" #1 prio=5 os_prio=0 tid=0x00007f8718009800 nid=0xb runnable [0x00007f871fe41000]
? java.lang.Thread.State: RUNNABLE
? ? at com.aibaobei.chapter2.eg2.UserDemo.main(UserDemo.java:9)
"VM Thread" os_prio=0 tid=0x00007f871806e000 nid=0xa runnable
這里的 VM Thread 一行的最后顯示 nid=0xa街望,這里 nid 的意思就是操作系統(tǒng)線程 id 的意思,而 VM Thread 指的就是垃圾回收的線程弟跑。
這里我們基本上可以確定灾前,當(dāng)前系統(tǒng)緩慢的原因主要是垃圾回收過(guò)于頻繁,導(dǎo)致 GC 停頓時(shí)間較長(zhǎng)孟辑。
我們通過(guò)如下命令可以查看 GC 的情況:
root@8d36124607a0:/# jstat -gcutil 9 1000 10
? S0? ? S1? ? E? ? ? O? ? ? M? ? CCS? ? YGC? ? YGCT? ? FGC? ? FGCT? ? GCT
? 0.00? 0.00? 0.00? 75.07? 59.09? 59.60? 3259? ? 0.919? 6517? ? 7.715? ? 8.635
? 0.00? 0.00? 0.00? 0.08? 59.09? 59.60? 3306? ? 0.930? 6611? ? 7.822? ? 8.752
? 0.00? 0.00? 0.00? 0.08? 59.09? 59.60? 3351? ? 0.943? 6701? ? 7.924? ? 8.867
? 0.00? 0.00? 0.00? 0.08? 59.09? 59.60? 3397? ? 0.955? 6793? ? 8.029? ? 8.984
可以看到哎甲,這里 FGC 指的是 Full GC 數(shù)量,這里高達(dá) 6793饲嗽,而且還在不斷增長(zhǎng)炭玫。從而進(jìn)一步證實(shí)了是由于內(nèi)存溢出導(dǎo)致的系統(tǒng)緩慢。
那么這里確認(rèn)了內(nèi)存溢出貌虾,但是如何查看你是哪些對(duì)象導(dǎo)致的內(nèi)存溢出呢吞加,這個(gè)可以 Dump 出內(nèi)存日志,然后通過(guò) Eclipse 的 Mat 工具進(jìn)行查看尽狠。
如下圖是其展示的一個(gè)對(duì)象樹(shù)結(jié)構(gòu):
經(jīng)過(guò) Mat 工具分析之后衔憨,我們基本上就能確定內(nèi)存中主要是哪個(gè)對(duì)象比較消耗內(nèi)存,然后找到該對(duì)象的創(chuàng)建位置袄膏,進(jìn)行處理即可践图。
這里主要是 PrintStream 最多,但是我們也可以看到沉馆,其內(nèi)存消耗量只有 12.2%码党。
也就是說(shuō),其還不足以導(dǎo)致大量的 Full GC悍及,此時(shí)我們需要考慮另外一種情況闽瓢,就是代碼或者第三方依賴的包中有顯示的 System.gc() 調(diào)用。
這種情況我們查看 Dump 內(nèi)存得到的文件即可判斷心赶,因?yàn)槠鋾?huì)打印 GC 原因:
[Full GC (System.gc()) [Tenured: 262546K->262546K(349568K), 0.0014879 secs] 262546K->262546K(506816K), [Metaspace: 3109K->3109K(1056768K)], 0.0015151 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [DefNew: 2795K->0K(157248K), 0.0001504 secs][Tenured: 262546K->402K(349568K), 0.0012949 secs] 265342K->402K(506816K), [Metaspace: 3109K->3109K(1056768K)], 0.0014699 secs] [Times: user=0.00
比如這里第一次 GC 是由于 System.gc() 的顯示調(diào)用導(dǎo)致的,而第二次 GC 則是 JVM 主動(dòng)發(fā)起的缺猛。
總結(jié)來(lái)說(shuō)缨叫,對(duì)于 Full GC 次數(shù)過(guò)多,主要有以下兩種原因:
代碼中一次獲取了大量的對(duì)象荔燎,導(dǎo)致內(nèi)存溢出耻姥,此時(shí)可以通過(guò) Eclipse 的 Mat 工具查看內(nèi)存中有哪些對(duì)象比較多。
內(nèi)存占用不高有咨,但是 Full GC 次數(shù)還是比較多琐簇,此時(shí)可能是顯示的 System.gc() 調(diào)用導(dǎo)致 GC 次數(shù)過(guò)多,這可以通過(guò)添加 -XX:+DisableExplicitGC 來(lái)禁用 JVM 對(duì)顯示 GC 的響應(yīng)。
CPU 過(guò)高
在前面第一點(diǎn)中婉商,我們講到似忧,CPU 過(guò)高可能是系統(tǒng)頻繁的進(jìn)行 Full GC,導(dǎo)致系統(tǒng)緩慢丈秩。
而我們平常也肯定能遇到比較耗時(shí)的計(jì)算盯捌,導(dǎo)致 CPU 過(guò)高的情況,此時(shí)查看方式其實(shí)與上面的非常類似蘑秽。
首先我們通過(guò) top 命令查看當(dāng)前 CPU 消耗過(guò)高的進(jìn)程是哪個(gè)饺著,從而得到進(jìn)程 id;然后通過(guò) top -Hp <pid> 來(lái)查看該進(jìn)程中有哪些線程 CPU 過(guò)高肠牲,一般超過(guò) 80% 就是比較高的幼衰,80% 左右是合理情況。
這樣我們就能得到 CPU 消耗比較高的線程 id缀雳。接著通過(guò)該線程 id 的十六進(jìn)制表示在 jstack 日志中查看當(dāng)前線程具體的堆棧信息塑顺。
在這里我們就可以區(qū)分導(dǎo)致 CPU 過(guò)高的原因具體是 Full GC 次數(shù)過(guò)多還是代碼中有比較耗時(shí)的計(jì)算了。
如果是 Full GC 次數(shù)過(guò)多俏险,那么通過(guò) jstack 得到的線程信息會(huì)是類似于 VM Thread 之類的線程严拒。
而如果是代碼中有比較耗時(shí)的計(jì)算,那么我們得到的就是一個(gè)線程的具體堆棧信息竖独。
如下是一個(gè)代碼中有比較耗時(shí)的計(jì)算裤唠,導(dǎo)致 CPU 過(guò)高的線程信息:
這里可以看到,在請(qǐng)求 UserController 的時(shí)候莹痢,由于該 Controller 進(jìn)行了一個(gè)比較耗時(shí)的調(diào)用种蘸,導(dǎo)致該線程的 CPU 一直處于 100%。
我們可以根據(jù)堆棧信息竞膳,直接定位到 UserController 的 34 行航瞭,查看代碼中具體是什么原因?qū)е掠?jì)算量如此之高。
不定期出現(xiàn)的接口耗時(shí)現(xiàn)象
對(duì)于這種情況坦辟,比較典型的例子就是刊侯,我們某個(gè)接口訪問(wèn)經(jīng)常需要 2~3s 才能返回。
這是比較麻煩的一種情況锉走,因?yàn)橐话銇?lái)說(shuō)滨彻,其消耗的 CPU 不多,而且占用的內(nèi)存也不高挪蹭,也就是說(shuō)亭饵,我們通過(guò)上述兩種方式進(jìn)行排查是無(wú)法解決這種問(wèn)題的。
而且由于這樣的接口耗時(shí)比較大的問(wèn)題是不定時(shí)出現(xiàn)的梁厉,這就導(dǎo)致了我們?cè)谕ㄟ^(guò) jstack 命令即使得到了線程訪問(wèn)的堆棧信息辜羊,我們也沒(méi)法判斷具體哪個(gè)線程是正在執(zhí)行比較耗時(shí)操作的線程。
對(duì)于不定時(shí)出現(xiàn)的接口耗時(shí)比較嚴(yán)重的問(wèn)題,我們的定位思路基本如下:首先找到該接口八秃,通過(guò)壓測(cè)工具不斷加大訪問(wèn)力度碱妆。
如果說(shuō)該接口中有某個(gè)位置是比較耗時(shí)的,由于我們的訪問(wèn)的頻率非常高喜德,那么大多數(shù)的線程最終都將阻塞于該阻塞點(diǎn)矾踱。
這樣通過(guò)多個(gè)線程具有相同的堆棧日志硫痰,我們基本上就可以定位到該接口中比較耗時(shí)的代碼的位置。
如下是一個(gè)代碼中有比較耗時(shí)的阻塞操作通過(guò)壓測(cè)工具得到的線程堆棧日志:
"http-nio-8080-exec-2" #29 daemon prio=5 os_prio=31 tid=0x00007fd08cb26000 nid=0x9603 waiting on condition [0x00007000031d5000]
? java.lang.Thread.State: TIMED_WAITING (sleeping)
? ? at java.lang.Thread.sleep(Native Method)
? ? at java.lang.Thread.sleep(Thread.java:340)
? ? at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
? ? at com.aibaobei.user.controller.UserController.detail(UserController.java:18)
"http-nio-8080-exec-3" #30 daemon prio=5 os_prio=31 tid=0x00007fd08cb27000 nid=0x6203 waiting on condition [0x00007000032d8000]
? java.lang.Thread.State: TIMED_WAITING (sleeping)
? ? at java.lang.Thread.sleep(Native Method)
? ? at java.lang.Thread.sleep(Thread.java:340)
? ? at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
? ? at com.aibaobei.user.controller.UserController.detail(UserController.java:18)
"http-nio-8080-exec-4" #31 daemon prio=5 os_prio=31 tid=0x00007fd08d0fa000 nid=0x6403 waiting on condition [0x00007000033db000]
? java.lang.Thread.State: TIMED_WAITING (sleeping)
? ? at java.lang.Thread.sleep(Native Method)
? ? at java.lang.Thread.sleep(Thread.java:340)
? ? at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
? ? at com.aibaobei.user.controller.UserController.detail(UserController.java:18)
從上面的日志你可以看出,這里有多個(gè)線程都阻塞在了 UserController 的第 18 行您机,說(shuō)明這是一個(gè)阻塞點(diǎn)速种,也就是導(dǎo)致該接口比較緩慢的原因胰苏。
某個(gè)線程進(jìn)入 WAITING 狀態(tài)
對(duì)于這種情況毕源,這是比較罕見(jiàn)的一種情況,但是也是有可能出現(xiàn)的秕豫,而且由于其具有一定的“不可復(fù)現(xiàn)性”朴艰,因而我們?cè)谂挪榈臅r(shí)候是非常難以發(fā)現(xiàn)的。
筆者曾經(jīng)就遇到過(guò)類似的這種情況混移,具體的場(chǎng)景是祠墅,在使用 CountDownLatch 時(shí),由于需要每一個(gè)并行的任務(wù)都執(zhí)行完成之后才會(huì)喚醒主線程往下執(zhí)行歌径。
而當(dāng)時(shí)我們是通過(guò) CountDownLatch 控制多個(gè)線程連接并導(dǎo)出用戶的 Gmail 郵箱數(shù)據(jù)毁嗦,這其中有一個(gè)線程連接上了用戶郵箱,但是連接被服務(wù)器掛起了回铛,導(dǎo)致該線程一直在等待服務(wù)器的響應(yīng)狗准。
最終導(dǎo)致我們的主線程和其余幾個(gè)線程都處于 WAITING 狀態(tài)。
對(duì)于這樣的問(wèn)題茵肃,查看過(guò) jstack 日志的讀者應(yīng)該都知道腔长,正常情況下,線上大多數(shù)線程都是處于 TIMED_WAITING 狀態(tài)验残。
而我們這里出問(wèn)題的線程所處的狀態(tài)與其是一模一樣的捞附,這就非常容易混淆我們的判斷。
解決這個(gè)問(wèn)題的思路主要如下:
①通過(guò) grep 在 jstack 日志中找出所有的處于 TIMED_WAITING 狀態(tài)的線程胚膊,將其導(dǎo)出到某個(gè)文件中故俐,如 a1.log,如下是一個(gè)導(dǎo)出的日志文件示例:
"Attach Listener" #13 daemon prio=9 os_prio=31 tid=0x00007fe690064000 nid=0xd07 waiting on condition [0x0000000000000000]
"DestroyJavaVM" #12 prio=5 os_prio=31 tid=0x00007fe690066000 nid=0x2603 waiting on condition [0x0000000000000000]
"Thread-0" #11 prio=5 os_prio=31 tid=0x00007fe690065000 nid=0x5a03 waiting on condition [0x0000700003ad4000]
"C1 CompilerThread3" #9 daemon prio=9 os_prio=31 tid=0x00007fe68c00a000 nid=0xa903 waiting on condition [0x0000000000000000]
②等待一段時(shí)間之后紊婉,比如 10s,再次對(duì) jstack 日志進(jìn)行 grep辑舷,將其導(dǎo)出到另一個(gè)文件喻犁,如 a2.log,結(jié)果如下所示:
"DestroyJavaVM" #12 prio=5 os_prio=31 tid=0x00007fe690066000 nid=0x2603 waiting on condition [0x0000000000000000]
"Thread-0" #11 prio=5 os_prio=31 tid=0x00007fe690065000 nid=0x5a03 waiting on condition [0x0000700003ad4000]
"VM Periodic Task Thread" os_prio=31 tid=0x00007fe68d114000 nid=0xa803 waiting on condition
③重復(fù)步驟 2,待導(dǎo)出 3~4 個(gè)文件之后肢础,我們對(duì)導(dǎo)出的文件進(jìn)行對(duì)比还栓,找出其中在這幾個(gè)文件中一直都存在的用戶線程。
這個(gè)線程基本上就可以確認(rèn)是包含了處于等待狀態(tài)有問(wèn)題的線程传轰。因?yàn)檎5恼?qǐng)求線程是不會(huì)在 20~30s 之后還是處于等待狀態(tài)的剩盒。
④經(jīng)過(guò)排查得到這些線程之后,我們可以繼續(xù)對(duì)其堆棧信息進(jìn)行排查慨蛙,如果該線程本身就應(yīng)該處于等待狀態(tài)辽聊,比如用戶創(chuàng)建的線程池中處于空閑狀態(tài)的線程,那么這種線程的堆棧信息中是不會(huì)包含用戶自定義的類的期贫。
這些都可以排除掉跟匆,而剩下的線程基本上就可以確認(rèn)是我們要找的有問(wèn)題的線程。
通過(guò)其堆棧信息通砍,我們就可以得出具體是在哪個(gè)位置的代碼導(dǎo)致該線程處于等待狀態(tài)了玛臂。
這里需要說(shuō)明的是,我們?cè)谂袛嗍欠駷橛脩艟€程時(shí)封孙,可以通過(guò)線程最前面的線程名來(lái)判斷迹冤,因?yàn)橐话愕目蚣艿木€程命名都是非常規(guī)范的。
我們通過(guò)線程名就可以直接判斷得出該線程是某些框架中的線程虎忌,這種線程基本上可以排除掉泡徙。
而剩余的,比如上面的 Thread-0呐籽,以及我們可以辨別的自定義線程名锋勺,這些都是我們需要排查的對(duì)象。
經(jīng)過(guò)上面的方式進(jìn)行排查之后狡蝶,我們基本上就可以得出這里的 Thread-0 就是我們要找的線程庶橱,通過(guò)查看其堆棧信息,我們就可以得到具體是在哪個(gè)位置導(dǎo)致其處于等待狀態(tài)了贪惹。
如下示例中則是在 SyncTask 的第 8 行導(dǎo)致該線程進(jìn)入等待了:
"Thread-0" #11 prio=5 os_prio=31 tid=0x00007f9de08c7000 nid=0x5603 waiting on condition [0x0000700001f89000]
? java.lang.Thread.State: WAITING (parking)
? ? at sun.misc.Unsafe.park(Native Method)
? ? at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
? ? at com.aibaobei.chapter2.eg4.SyncTask.lambda$main$0(SyncTask.java:8)
? ? at com.aibaobei.chapter2.eg4.SyncTask$$Lambda$1/1791741888.run(Unknown Source)
? ? at java.lang.Thread.run(Thread.java:748)
死鎖
對(duì)于死鎖苏章,這種情況基本上很容易發(fā)現(xiàn),因?yàn)?jstack 可以幫助我們檢查死鎖奏瞬,并且在日志中打印具體的死鎖線程信息枫绅。
如下是一個(gè)產(chǎn)生死鎖的一個(gè) jstack 日志示例:
可以看到,在 jstack 日志的底部硼端,其直接幫我們分析了日志中存在哪些死鎖并淋,以及每個(gè)死鎖的線程堆棧信息。
這里我們有兩個(gè)用戶線程分別在等待對(duì)方釋放鎖珍昨,而被阻塞的位置都是在 ConnectTask 的第 5 行县耽,此時(shí)我們就可以直接定位到該位置句喷,并且進(jìn)行代碼分析,從而找到產(chǎn)生死鎖的原因兔毙。
小結(jié)
本文主要講解了線上可能出現(xiàn)的五種導(dǎo)致系統(tǒng)緩慢的情況唾琼,詳細(xì)分析了每種情況產(chǎn)生時(shí)的現(xiàn)象,已經(jīng)根據(jù)現(xiàn)象我們可以通過(guò)哪些方式定位得到是這種原因?qū)е碌南到y(tǒng)緩慢澎剥。
簡(jiǎn)要的說(shuō)锡溯,我們進(jìn)行線上日志分析時(shí),主要可以分為如下步驟:
①通過(guò) top 命令查看 CPU 情況哑姚,如果 CPU 比較高祭饭,則通過(guò) top -Hp <pid> 命令查看當(dāng)前進(jìn)程的各個(gè)線程運(yùn)行情況。
找出 CPU 過(guò)高的線程之后蜻懦,將其線程 id 轉(zhuǎn)換為十六進(jìn)制的表現(xiàn)形式甜癞,然后在 jstack 日志中查看該線程主要在進(jìn)行的工作。
這里又分為兩種情況:
如果是正常的用戶線程宛乃,則通過(guò)該線程的堆棧信息查看其具體是在哪處用戶代碼處運(yùn)行比較消耗 CPU悠咱。
如果該線程是 VM Thread,則通過(guò) jstat -gcutil 命令監(jiān)控當(dāng)前系統(tǒng)的 GC 狀況征炼。
然后通過(guò) jmap dump:format=b,file= 導(dǎo)出系統(tǒng)當(dāng)前的內(nèi)存數(shù)據(jù)析既。
導(dǎo)出之后將內(nèi)存情況放到 Eclipse 的 Mat 工具中進(jìn)行分析即可得出內(nèi)存中主要是什么對(duì)象比較消耗內(nèi)存,進(jìn)而可以處理相關(guān)代碼谆奥。
②如果通過(guò) top 命令看到 CPU 并不高眼坏,并且系統(tǒng)內(nèi)存占用率也比較低。此時(shí)就可以考慮是否是由于另外三種情況導(dǎo)致的問(wèn)題酸些。
具體的可以根據(jù)具體情況分析:
如果是接口調(diào)用比較耗時(shí)宰译,并且是不定時(shí)出現(xiàn),則可以通過(guò)壓測(cè)的方式加大阻塞點(diǎn)出現(xiàn)的頻率魄懂,從而通過(guò) jstack 查看堆棧信息沿侈,找到阻塞點(diǎn)。
如果是某個(gè)功能突然出現(xiàn)停滯的狀況市栗,這種情況也無(wú)法復(fù)現(xiàn)缀拭,此時(shí)可以通過(guò)多次導(dǎo)出 jstack 日志的方式對(duì)比哪些用戶線程是一直都處于等待狀態(tài),這些線程就是可能存在問(wèn)題的線程填帽。
如果通過(guò) jstack 可以查看到死鎖狀態(tài)蛛淋,則可以檢查產(chǎn)生死鎖的兩個(gè)線程的具體阻塞點(diǎn),從而處理相應(yīng)的問(wèn)題篡腌。
本文主要是提出了五種常見(jiàn)的導(dǎo)致線上功能緩慢的問(wèn)題褐荷,以及排查思路。當(dāng)然嘹悼,線上的問(wèn)題出現(xiàn)的形式是多種多樣的诚卸,也不一定局限于這幾種情況葵第。
如果我們能夠仔細(xì)分析這些問(wèn)題出現(xiàn)的場(chǎng)景绘迁,就可以根據(jù)具體情況具體分析合溺,從而解決相應(yīng)的問(wèn)題。