網(wǎng)站制作價(jià)格和流程云浮新增確診病例30例
CPU 使用率在系統(tǒng)監(jiān)控中是一個(gè)非常重要的指標(biāo)。對(duì)于大多數(shù) Web 應(yīng)用來(lái)說(shuō),它們往往是 IO 密集型的,因此只會(huì)在某些時(shí)刻可能會(huì)出現(xiàn) CPU 突然飆升的情況,隨后很快就恢復(fù)正常。然而,當(dāng)收到報(bào)警并想要排查問(wèn)題時(shí),CPU 飆升的情況可能已經(jīng)過(guò)去,無(wú)法直接查看當(dāng)時(shí) Java 進(jìn)程內(nèi)的線程堆棧信息。
為了解決這個(gè)問(wèn)題,本文實(shí)現(xiàn)了一個(gè)小工具,它在 CPU 飆升時(shí)自動(dòng)保存堆棧信息,方便后續(xù)問(wèn)題的追蹤。
該工具實(shí)現(xiàn)了兩個(gè)主要功能:
- 當(dāng) CPU 使用率達(dá)到預(yù)設(shè)的閾值時(shí),自動(dòng)保存當(dāng)前 Java 進(jìn)程的線程堆棧信息;
- 輸出占用 CPU 使用率最高的線程 ID。
本文選擇在 Linux 系統(tǒng)上實(shí)現(xiàn)這個(gè)工具,因?yàn)榇蠖鄶?shù)應(yīng)用都是部署在 Linux 環(huán)境中(本來(lái)想實(shí)現(xiàn)一個(gè) MacOS 版本的腳本,但搗鼓了半天,由于格式以及函數(shù)存在差異,所以就沒(méi)繼續(xù)糾結(jié)了)
腳本如下:
#!/bin/bash
if [ $# -ne 1 ]; thenecho "Usage: $0 <j_pid>"exit 1
fij_pid=$1
threshold=90while true; docpu_info=$(ps -p "$j_pid" -o %cpu=)cpu_usage=$(sed 's/%//g' <<< "$cpu_info")current_time=$(date +'%Y-%m-%d %H:%M:%S')echo "[-----$current_time] Current CPU Usage: $cpu_usage%"if [ $(echo "$cpu_usage > $threshold" | bc -q) -eq 1 ]; thenthread_id=$(top -b -n 1 -H -p $j_pid | grep -E "^\s*[0-9]+" | sort -k9 -r | head -n 1 | awk '{print $1}')echo "[-----$current_time] Detected! PID: $j_pid ; Thread ID: $thread_id"jstack_output=$(jstack -l $j_pid)jstack_filename="jstack_$(date +'%Y%m%d%H%M%S').txt"echo "$jstack_output" > "$jstack_filename"echo "[$current_time] jstack_filename: $jstack_filename"fisleep 10
done
可以看看效果,這是模擬的消耗 CPU 的 Java 代碼:
/*** @author dongguabai* @date 2023-07-21 13:11*/
public class Test {public static void doSth() {while (true) {double x = Math.random() * Math.random();}}public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {doSth();}}, "highcpu_thread").start();}
}
執(zhí)行 Java 代碼:
[root@MiWiFi-R4CM-srv javaTest]# javac Test.java
[root@MiWiFi-R4CM-srv javaTest]# java Test
執(zhí)行腳本:
[root@MiWiFi-R4CM-srv javaTest]# sh monitor.sh 9581
[-----2023-07-21 01:37:12] Current CPU Usage: 99.3%
[-----2023-07-21 01:37:12] Detected! PID: 9581 ; Thread ID: 9590
[2023-07-21 01:37:12] jstack_filename: jstack_20230721013713.txt
查看線程 ID:
查看線程堆棧:
"Attach Listener" #9 daemon prio=9 os_prio=0 tid=0xb768fc00 nid=0x25a0 waiting on condition [0x00000000]java.lang.Thread.State: RUNNABLE"DestroyJavaVM" #8 prio=5 os_prio=0 tid=0xb7607400 nid=0x256e waiting on condition [0x00000000]java.lang.Thread.State: RUNNABLE"highcpu_thread" #7 prio=5 os_prio=0 tid=0xb768ec00 nid=0x2576 runnable [0xa4bb8000]java.lang.Thread.State: RUNNABLEat Test.doSth(Test.java:13)at Test$1.run(Test.java:20)at java.lang.Thread.run(Thread.java:748)
可以看到,效果還是可以的。
注意事項(xiàng)
在生產(chǎn)環(huán)境中使用此工具時(shí),需要注意以下幾點(diǎn):
jstack
命令可能對(duì)系統(tǒng)性能產(chǎn)生一定影響,需要謹(jǐn)慎使用并避免頻繁執(zhí)行- 如果 Java 進(jìn)程非常大,有很多線程,那么收集線程堆棧信息的操作可能會(huì)耗費(fèi)較多的系統(tǒng)資源
- 觸發(fā)腳本時(shí) Java 進(jìn)程本身已經(jīng)處于高負(fù)載狀態(tài),運(yùn)行
jstack
命令可能會(huì)導(dǎo)致 Java 進(jìn)程的響應(yīng)時(shí)間增加 - 最好設(shè)置一個(gè)觸發(fā)閾值,頻繁地運(yùn)行
jstack
命令收集線程堆棧信息,可能會(huì)在一定程度上增加系統(tǒng)負(fù)擔(dān)
- 腳本最好保存在一個(gè)不容易被意外殺掉的地方,以確保在 CPU 飆升時(shí)能正常執(zhí)行。
- 在應(yīng)用剛啟動(dòng)的時(shí)候,可能由于初始化等操作導(dǎo)致短暫的 CPU 飆升,因此腳本可設(shè)置延遲執(zhí)行,避免誤報(bào)。