目錄
問題現場
測試環境tomcat進程占用CPU一直持續99%,但是通過jstack查看log,也沒有任何線程死鎖的情況。
此時通過$catalina_home/bin/shutdown.sh腳本無法正常停止tomcat。
這是什么原因?
線程死鎖 vs 線程死循環
驗證線程死鎖不會導致CPU持續高負載
// 驗證線程死鎖是否會導致CPU占用率一直居高不下
public class LockTest {
private Object alock = new Object(); // a鎖
private Object block = new Object(); // b鎖
public static void main(String[] args) {
new LockTest().start();
}
private void start() {
new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start();
}
private void sleepOfSeconds(int seconds) {
try {
Thread.sleep(seconds * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 在線程A中先獲取a鎖,再獲取b鎖
class ThreadA implements Runnable {
private String name = "ThreadA";
@Override
public void run() {
synchronized (alock) {
System.out.println(name + " get alock");
sleepOfSeconds(50);
}
System.out.println(name + " release alock");
System.out.println(name + " waiting block");
synchronized (block) {
System.out.println(name + " get block");
}
System.out.println(name + " release block");
}
}
// 在線程B中先獲取b鎖,再獲取a鎖
class ThreadB implements Runnable {
private String name = "ThreadB";
@Override
public void run() {
synchronized (block) {
System.out.println(name + " get block");
sleepOfSeconds(10);
}
System.out.println(name + " release block");
System.out.println(name + " waiting alock");
synchronized (alock) {
System.out.println(name + " get alock");
}
System.out.println(name + " release alock");
}
}
}
線程進入死鎖狀態時,不會導致CPU持續高負載,實際上當線程進入死鎖之后是等待獲取對象所被執行,此時CPU是空閑的。
導致CPU負載持續高的原因是線程進入了死循環,導致CPU持續在工作,此時線程的狀態應該是Runnable,而不是Blocked。
排查Java進程導致CPU持續高的方法
在Linux環境下,通過如下步驟可以實現對Java進程CPU持續高負載的問題排查:
- 通過
jps
命令找到Java進程ID,並使用top
命令確定CPU占用高的進程是否為jps
命令找到的Java進程。 - 通過
ps -mp pid -o THREAD,tid,time
命令查看進程的線程列表,找到CPU占用最高的線程ID,並使用printf "%x\n" tid
命令輸出線程ID的16進制格式:tid_hex 。 - 通過
jstack pid |grep tid_hex -A 30
查看線程堆棧信息,找到處於Runnable
狀態的線程中執行的代碼片段,就可以分析出對應導致線程死循環的原因了。
可以將上述命令整理成一個腳本工具,用於臨時排查CPU高的問題,詳見:https://raw.githubusercontent.com/nuccch/iToolBox/master/shell/show_java_process_thread_stack.sh 。
Tomcat的CPU占用高的原因總結
- 線程死鎖和線程死循環不是一個概念,千萬不要弄錯。
- 通常來講,對於部署到Tomcat中的應用程序,排除程序代碼進入死循環的原因之外,會導致Tomcat進程CPU持續高負載的可能因素是存在大量的TCP連接請求(並發很大)。
- 由於應用程序出現堆內存空間不夠用導致頻繁GC,也會導致CPU使用率高。
- 如果應用日志輸出非常頻繁,也會導致CPU使用率持續高。
【參考】
https://www.jianshu.com/p/3160ba8e150d 記一次tomcat cpu占用率過高的問題排查
http://www.blogjava.net/hankchen/archive/2012/05/09/377735.html 線上應用故障排查之一:高CPU占用