相關歷史文章(閱讀本文前,您可能需要先看下之前的系列?)
國內最全的Spring?Boot系列之三
2020上半年發文匯總「值得收藏」
GraphQL的探索之路?–?SpringBoot集成GraphQL小栗子篇二?-?第315篇
GraphQL的探索之路?–?SpringBoot集成GraphQL之Query篇三?-?第316篇
GraphQL的探索之路?–?SpringBoot集成GraphQL之Mutation篇四?-?第317篇
RocketMQ安裝Linux/Mac/Window - 第318篇
需求緣起
在群里有這么一段對話:
愿得一人心:服務器cpu load偏高,無從下手,哪位大佬能提供點兒幫助
不老神話:top 查看偏高的進程
老鼠愛上貓:百度谷歌啊? 查問題也是程序員必備技能之一
莫欺少年窮:要相信你肯定不是第一個遇到這個問題的
愿得一人心:查了,不頂用。
?????? 問問題真的把問題說清楚,不然解答的人也是一臉懵逼,很多人都是愿意解答問題的,但是問問題的人問的模棱兩可,導致沒有人敢解答。
正文開始? ?? ?
悟纖:師傅,師傅,緊急求助。
師傅:徒兒,何事如此之著急?
悟纖:我發現我寫的代碼導致CPU持續為99%,但是項目路這么大,我也不知道是哪塊代碼導致的。
師傅:徒兒,那你得看看是哪個線程里的邏輯導致了CPU飆高。
悟纖:那我們怎么找到這個線程在運行的堆棧信息吶?
師傅:jstack呀,你難道沒有聽過嘛?
悟纖:知道到知道這個指令,但是查看了些資料,都是說的不清楚,看完我也是一塌糊涂吶。
師傅:看來得為師給你好好講講了。
悟纖:還是喜歡師傅的講解方式,簡單、詳細、一語道破天機。
一、排查步驟
師傅:要找回線程的堆棧信息,主要是利用java給我們提供的調優工具jstack,我們看下具體的一個步驟:
(1)使用命令top -p ,顯示你的java進程的cpu情況,pid是你的java進程號,比如14203。(使用jps可以獲取到java的進程id 或者top直接查看)
(2)按H,獲取每個線程的CPU情況。(shirt+H)
(3)找到內存和cpu占用最高的線程tid,比如14204。
(4)轉為十六進制得到 377C ,此為線程id的十六進制表示。
(5)執行 jstack |grep -A 10 ,得到線程堆棧信息中1371這個線程所在行的后面10行。(注意:如果十六進制由字母的要小寫)
# Jstack 14203 |? grep -A 10 377c
(6)查看對應的堆棧信息找出可能存在問題的代碼。
師傅:看起來是不是很復雜,描述的羅里吧嗦的。
悟纖:看著就暈頭轉向的。
師傅:一句話概述。
一句話:通過top找到線程id,通過jstack找到線程的堆棧信息。
二、小試牛刀:Linux環境
2.1 準備工作
?????? 我們編寫一個小代碼EndlessLoopTest用于模擬導致cpu過高(源碼在最后提供),編譯成class文件,將我們的class文件放到Linux上。
?????? 注意:存放的需要根據包名建立目錄結構,否則無法運行此class文件。
比如包名是com.kfit.jvm那么就需要創建一個目錄結構com/kfit/jvm,然后把EndlessLoopTest.class放到這里面。
執行class:
java com.kfit.jvm.EndlessLoopTest
2.2 排查實戰
2.2.1 使用top找到cpu飆高的java進程ID
?????? 首先我們需要找到java進程ID,使用top指令:
#top
?????? 我們一看就看到了pid為14203的java的CPU使用率是99.7%并且持續飆高,那么這個肯定是代碼寫的有問題了。
2.2.2 使用top -p 顯示進程情況
?????? 我們使用如下命名查看java進程的情況:
#top -p 14203
?????? 通過-p的方法就是只顯示了指定進程的信息。
2.2.3 按H查看線程的CPU情況
?????? 在上面的界面中使用shirt+H進行查看各個線程的CPU情況:
?????? 注意這里的PID實際對應的是線程的十進制的tid,通過上面我們可以看到CPU使用很高的線程ID是14204。
2.2.4 線程十進制轉換為十六進制
?????? 我們將獲取到的線程十進制的轉換為十六進制:
14204(十進制) = 377C(十六進制)
?????? 怎么轉你不知道嘛?
方式一:找個轉換網站
https://tool.oschina.net/hexconvert/
https://tool.lu/hexconvert/(這個強大,可以一下子轉換出來好幾個進制的)
方式二:Linux/Mac的printf
?????? 使用Linux/Mac的printf即可:
printf %x 14204? && echo
方式三:Linux/Mac的echo
?????? Echo也是很強大的:
echo 'ibase=10;obase=16;14204'|bc
方式四:python的hex
?????? 利用python的轉換hex將十進制轉換為十六進制:
悟纖:師傅,你這是要飄了,要跑題了。
師傅:哈哈,師傅這是已經超神了。
2.2.5 執行jstack得到線程的堆棧信息
執行 jstack | grep -A 10 ,得到線程堆棧信息:
#jstack 14203 |? grep -A 10 377c
注意:小c、小c、小c,重要的事情說3遍。
2.2.6查看對應的堆棧信息問題排查
?????? 我上面的堆棧信息可以看出出現問題的類是EndlessLoopTest.java,代碼行號是13:
源碼:
package com.kfit.jvm;import java.net.Socket;/** * 死循環測試 */public class EndlessLoopTest { public void test(){ int random = (int) (java.lang.Math.random() * 1000); while (random < 100) { random = random * 10; } System.out.println(random); } public static void main(String[] args) { EndlessLoopTest test = new EndlessLoopTest(); for(int i=0;i<5000;i++){ test.test(); } }}
?????? 我們分析這個while循環,看著挺正常的,但是如果當random為0的時候,不就是陷入死循環了嗎。
悟纖小結
悟纖:師傅,你真是我的偶像,講解的如此之詳細,我要是再不懂,看來只能退出編程界了。
師傅:徒兒,言重了。雖然為師已經介紹的很詳細了,但是難免在實際使用的時候會踩到一些坑。
悟纖:那為了避免大家采坑,我和大家總結下。
核心就是兩大步驟:
(1)通過top找到線程id。
通過Linux系統的top命名找到cpu飆高的進程id;通過top -p 找到該進程id的cpu信息;然后配合shirt+H命名,就可以找到CPU線程高的線程ID:通過工具類將十進制的線程id轉換為十六進制的。
(2)通過jstack找到線程的堆棧信息。
?????? 通過jstack | grep -A 10 就可以找到線程的堆棧信息。
通過top找到線程id,通過jstack找到線程的堆棧信息。
我就是我,是顏色不一樣的煙火。
我就是我,是與眾不同的小蘋果。
à悟空學院:https://t.cn/Rg3fKJD
學院中有Spring?Boot相關的課程!點擊「閱讀原文」進行查看!
SpringBoot視頻:http://t.cn/A6ZagYTi
Spring?Cloud視頻:http://t.cn/A6ZagxSR
SpringBoot?Shiro視頻:http://t.cn/A6Zag7IV
SpringBoot交流平臺:https://t.cn/R3QDhU0
SpringData和JPA視頻:http://t.cn/A6Zad1OH
SpringSecurity5.0視頻:http://t.cn/A6ZadMBe
Sharding-JDBC分庫分表實戰:
http://t.cn/A6ZarrqS
分布式事務解決方案「手寫代碼」:
http://t.cn/A6ZaBnIr
深入理解JVM內存模型/調優實戰:
http://t.cn/A6wWMVqG