本文為博主原創,未經允許不得轉載:
目錄:
1. 定位消耗cpu 的服務進程和線程
2. 定位FGC 的原因
3. 定位jvm 參數是否導致FGC
4. 調試最優解的 jvm 配置
描述:項目中存在一個后台服務,該后台服務主要用來執行定時任務與kafka 中間件消息的消費。在壓測環境上部署時,觀察到 cpu 的使用率 異常,竟然達到了 600%, 所以記錄下該問題的定位和解決過程,以幫助更多的伙伴。
1. 定位消耗cpu 的服務進程和線程
使用top 命令查看 服務器的cpu使用情況
top
獲取 top 中cpu 占用率最高的進程的pid ,通過 top -H -P pid 獲取該進程對應所有線程的使用情況
top -H -p pid
通過上面命令得到使用cpu 最高的線程號 threadId ,將線程號通過命令轉換為十六進制:
printf "%x\n" threadId
通過以上命令獲取到jvm中對應的 nid , 通過 jstack 查看該 threadId 線程的堆棧信息:
jstack -l pid| grep -10 nid
通過以上命令判斷該線程 執行任務的內容,從而推斷導致cpu飆升的原因。
項目中碰到導致cpu飆升的原因是 存在較多的 FGC 線程,從而懷疑 是 項目內部不斷FGC 導致CPU飆升,從而監控項目的FGC 頻率
2. 定位FGC 的原因
通過 jstat 命令查看 FGC 的頻率。
jstat -gc pid 3000
發現 FGC 每隔三秒要進行9次左右的FGC垃圾回收。由於FGC 會導致STW (stop the world)現象,及服務不可用。
需要定位 jvm 內存中的堆棧內容與線程。通過 Visualm 遠程監控服務的jvm 性能,jvisualm 使用可參考這篇文章 (https://www.cnblogs.com/zjdxr-up/p/14916455.html),通過 jvisualm 查看服務當前存在的線程和堆內容。通過jvisualm 將堆內容與線程進行dump 之后,發現並未存在異常的內容。且 定時任務與kafka 都是開源的成熟框架,應該不會是導致頻繁FGC 的主要原因。
所以懷疑可能 服務的 jvm 參數配置存在問題,因為如果 jvm 參數設置不合理,當老年代的內存達到一定比例,則會進行FGC。下一步定位 jvm 的參數是否是主要原因。
3. 定位jvm 參數是否導致FGC
由於我們的服務啟動都會設置 最大堆內存和初始化堆內存等參數,所以需要調整 不同jvm 參數 時,服務內部的FGC 情況。
以下為我們服務設置的 JVM 相關參數
-Xmn512m -Xms512m -Xmx2048m -XX:NewSize=512M -XX:MaxNewSize=512M -XX:-UseAdaptiveSizePlicy -XX:ParallelGCThreads=16 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=15
為了形成做對比,采取 java -jar 的方式啟動服務,不手動設置jvm相關配置,使用 JVM 默認的配置,進行觀察是否有變化。
通過 java -jar 方式啟動,使用默認配置之后,再采用 top 觀察cpu 使用 與 jstat 觀察 FGC 頻率,發現 cpu 的使用率降了下來,恢復了正常狀態。
4. 調試最優解的 jvm 配置
獲取java -jar 服務啟動的進程, 再使用 jinfo 命令 查看JVM 默認的配置,並修改以上jvm 的配置。我們服務器內存均為 32G,默認最大堆內存為 服務器內存的四分之一,即最大堆內存為 8G 。其余參數可根據最大堆內存進行推算出來,通常初始化內存與最大堆內存使用相同的配置。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代后,將會減小年老代大小。-Xmn 此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8。年輕代大小 為 3G; 修改后的 jvm 配置參數如下:
-Xmn3072m -Xms8192m -Xmx8192m -XX:NewSize=3072M -XX:MaxNewSize=3072M -XX:-UseAdaptiveSizePlicy -XX:ParallelGCThreads=16 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=15
補充:為了調試出相對比較好的jvm配置,將最大堆內存進行了多個配置對比,即從最大堆配置的值依次遞增和遞減 512M 之后,觀察性能,發現默認配置依然最優解,所以才用默認配置作為服務啟動的jvm配置