在生產環境中,有時會遇到一些CPU占用過高,一直下不去的場景。出現這種情況,可能會導致服務對外中斷,服務器超負荷運行影響硬件壽命。這篇文章從實踐出發,一步一步地分析如何使用 ?top? ??和? ?jstack? ?命令快速定位問題代碼位置。
一、top命令
? top ? (table of processes) is a ? ?task manager? ?? program, found in many ? ?Unix-like? ? operating systems, that displays information about CPU and memory utilization.
? ?維基百科? ??解釋到,top (進程表)是一個任務管理器程序,可以在許多類 ? ?unix? ?操作系統中找到,它顯示有關 CPU 和內存使用情況的信息。
同時,top命令是linux下常用的性能分析工具,能夠實時顯示系統中各個進程的資源占用狀況,類似于windows的任務管理器。比較準確的說,top命令提供了實時的對系統處理器的狀態監視.它將顯示系統中CPU最“敏感”的任務列表.該命令可以按? CPU使用 ?、? 內存使用 ?和? 執行時間 ?對任務進行排序。
上圖是一個? ?centos 7? ? 系統,執行top命令后瞬時的展示信息。
- 第一行描述系統的信息,包括開機時間、用戶登錄、CPU整體負載
- 第二行描述任務總體執行情況,即各類進程占比情況
- 第三行描述CPU總體信息,比如空閑占比等
- 第四行描述物理內存占比情況
- 第五行描述交換區占比情況
- 動態的列表描述的詳細進程各項信息,比如? PID ? 進程ID, ? %CPU ? 進程占用CPU的使用率,? TIME+ ? 該進程啟動后占用的總的CPU時間,即占用CPU使用時間的累加值。
一般linux系統會自帶top命令,還有另外一個命令行工具 ? ?htop? ?,有時需要自行安裝,它與傳統的 top 命令功能一樣,但它有更加強大的功能及能顯示更多的信息。
二、 jstack命令
? jstack ? prints JAVA stack traces of Java threads for a given Java process or core file or a remote debug server。
? ?oracle官網? ?解釋到,Jstack 打印給定 Java 進程或核心文件或遠程調試服務器的 Java 線程的 Java 堆棧跟蹤。
對于每個 Java 框架,使用jstack指令將打印完整的類名、方法名、“ bci”(字節碼索引)和行號(如果可用)。使用-m 選項,jstack 將打印所有線程的 Java 和本機幀以及“ pc”(程序計數器)。
用法很簡單,如下圖。
一般說,我們會先使用? ?jps? ?命令顯示當前所有java進程pid的命令。
$jps
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(
img-XZxLXjOe-1628255428026)(https://upload-images.jianshu.io/upload_images/26464854-8cd522cd811debd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
然后再執行對執行進程ID 執行 jstack。
$ jstack 15673
如果明確自己的Java進程ID,當然直接使用 jstack指令即可。
三、實戰
3.1 實戰之前準備
先寫一個簡單的榨干CPU的例子? ?CpuDryOutExample.java? ?,內容相當的easy,就是在無中斷條件的循環語句中,不斷地執行打印內容操作。
?寫個例子?
public class CpuDryOutExample {
public static void dryOut() {
while (true) {
System.out.println("dry out utilization rate of cpu");
//do something
}
}
public static void main(String[] args) {
CpuDryOutExample.dryOut();
}
}
?編譯?
$javac CpuDryOutExample.java
輸出? ?CpuDryOutExample.class? ?,注意最好去掉java文件里面的package,否則編譯過程中很有可能會報找不到main方法,當然你編譯時特殊處理也行。
?運行?
將? ?CpuDryOutExample.class? ??上傳到? ?Centos 7? ? 服務器上,該服務器已經預先配置好了Java運行環境(JRE),然后執行下面命令運行程序。
$java CpuDryOutExample
此時屏幕瘋狂輸入? dry out utilization rate ?
3.2 定位問題
定位的核心是三個步驟
- 找到CPU占用率比較高的進程ID
- 在指定進程ID的進程中尋找進程CPU占用率比較高的線程ID
- 通過線程ID去搜索打印出的進程堆棧日志,定位到具體的問題
?找到CPU占用率比較高的進程ID?
在終端上輸入? ?top? ?命令
$top
可以明顯的看出PID為9573的進程CPU占用率最高,我們使用? ?htop? ?命令會更加直觀一點。
?查看進程里面線程運行的信息?
我們都知道線程是處理器任務調度和執行的基本單位,一個進程下是是包含多個線程。進程粒度還是過大,不便于我們定位到具體的代碼位置,我們需要找到具體是哪個線程過度使用CPU。
我們還是使用? ?top? ?命令。
//-H 顯示線程信息,-p指定pid jstack 線程ID
#$top -Hp 9573
圖上可以真正的看出,使CPU使用率暴漲的罪魁禍首是線程 9574。當然使用? ?htop? ?我們也能很快的定位到具體線程。
?分析過濾定位問題?
因為線程ID在堆棧日志中是以16進制呈現,我們先進行進制轉換。
$printf %s 9574
然后打印堆棧日志到臨時文件1.txt
# 注意是進程ID
$jstack 9573 > 1.txt
然后在文件中搜索線程所在位置
//在文件中搜索過濾并打印30條數據
$cat 1.txt | grep -A 30 2566
可以清楚的看到,紅框位置就是具體問題代碼。
四、總結
除了使用? ?top/htop + jstack? ?? 命令的方式,我們也可以使用? ?JMC? ??快速定位CPU占用率過高的問題,雖然? ?JMC? ?確實比前者更加簡單高效,但是只能使用JMX實現遠程連接,如果部署的服務沒有啟用JMX是用不上這個工具。
那么回到問題的根源,什么場景會導致CPU占用率過高呢?
序列化和反序列(使用合理的類庫)
正則表達式(回溯導致,避免回溯)
頻繁GC,GC線程頻發執行垃圾回收算法(降低GC頻率)
頻繁 的線程上下文切換(降低切換的頻率,根據業務合理建立線程池)
無限while循環(盡量有限循環,即設置中斷條件,讓循環執行的慢一點,即? ?Thead.yield? ?)
頻繁創建新對象(合理使用單例)
原文鏈接:
https://blog.51cto.com/u_15477630/5051043?utm_source=tuicool&utm_medium=referral






