jstack分析線程快照的三步曲及CPU占用過(guò)高和死鎖問(wèn)題的排查

jstack(Stack Trace For Java, 官方鏈接)用于生成java虛擬機(jī)某個(gè)進(jìn)程在當(dāng)前時(shí)刻的線程快照(一般稱為threaddump或javacore文件,由線程的調(diào)用堆棧組成),用來(lái)定位線程長(zhǎng)時(shí)間停頓的原因,如死循環(huán)、死鎖等。

一般在用該工具時(shí)主要分為三步:

  1. 獲取進(jìn)程id

    方法1: jps -l

    方法2: ps -ef|grep java

    方法3: lsof -i:<port>

    上述三個(gè)方法根據(jù)具體情況選擇使用,方法1最簡(jiǎn)便(適合java進(jìn)程少的情況),方法2信息更詳盡,方法3則適合有多個(gè)java進(jìn)程,根據(jù)方法1可能分辨不出來(lái)想找的進(jìn)程,而需要通過(guò)端口號(hào)進(jìn)行定位。

  2. 獲取進(jìn)程中的線程狀態(tài)

    top -Hp <pid>

    注意該命令只能在linux中使用,而在macOS上不能使用。因?yàn)閘inux將線程作為輕量級(jí)進(jìn)程也分配了pid,而macOS并沒(méi)有這么處理。

    如果通過(guò)上述命令我們發(fā)現(xiàn)進(jìn)程中的某個(gè)線程指標(biāo)不正常,想重點(diǎn)關(guān)注,這時(shí)需要將線程的pid通過(guò)下面命令轉(zhuǎn)化為十六進(jìn)進(jìn)制,方便在線程快照信息中查找。

? echo 'obase=16; ibase=10; <pid>' | bc | tr '[A-Z]' '[a-z]'

  1. 查看并分析線程快照信息

    jstack <pid>命令可以在控制臺(tái)打印線程快照信息, jstack <pid> > <path>可以將線程快照信息輸出到文件。如下為打印出來(lái)的一條線程快照,由10個(gè)部分組成。

    image-20181111102804259
    1. 線程名稱,如果程序中沒(méi)有顯示給線程命名則顯示默認(rèn)名稱
    2. 線程序號(hào),相當(dāng)于程序所有線程的一個(gè)編號(hào)
    3. 線程優(yōu)先級(jí),java中線程的默認(rèn)優(yōu)先級(jí)為5
    4. 線程系統(tǒng)優(yōu)先級(jí)
    5. 線程id
    6. 線程native id,在linux中對(duì)應(yīng)線程的輕量級(jí)進(jìn)程id,十六進(jìn)制,通過(guò)該字段都與top命令中的線程對(duì)應(yīng)起來(lái)。
    7. 線程描述
    8. 線程棧的起始地址
    9. 線程狀態(tài)
    10. 線程執(zhí)行堆棧,具體到代碼的行數(shù)

    需要注意的是執(zhí)行該命令時(shí)當(dāng)前用戶必須為啟動(dòng)該進(jìn)程的用戶,否則會(huì)失敗,即使是root用戶也不行。

接下來(lái)將通過(guò)兩個(gè)實(shí)例來(lái)對(duì)該工具的使用進(jìn)行詳細(xì)說(shuō)明:

實(shí)例1: 利用線程快照分析CPU占用過(guò)高的原因

用于演示CPU示例代碼如下: 啟動(dòng)三個(gè)線程,且某個(gè)線程獲取唯一的鎖后一直沒(méi)有釋放,可想而知,另外兩個(gè)線程將處于阻塞狀態(tài)。

import java.util.UUID;

public class HighCPUCase {
    public static Object lock = new Object();

    public static void main(String[] args) {
        new Thread(new Task(), "task1").start();
        new Thread(new Task(), "task2").start();
        new Thread(new Task(), "task3").start();
    }

    static class Task implements Runnable{
        @Override
        public void run(){
            synchronized (lock){
                while (true){
                    System.out.println(UUID.randomUUID().toString());
                }
            }
        }
    }
}

現(xiàn)在用文章開(kāi)始的三步曲來(lái)驗(yàn)證我們的猜想。

  1. 首先獲取進(jìn)程id

    image-20181110225814867
  2. 獲取進(jìn)程中消耗CPU最高的線程

    image-20181110230019853

    將23068用如下命令轉(zhuǎn)化為16進(jìn)制后為5a1c

    image-20181110230321690
  3. 查看并分析線程快照

    image-20181110231223530

    ? 上圖中藍(lán)色線框?yàn)槲覀冃枰攸c(diǎn)關(guān)注的內(nèi)容,可以看到5a1c(由第2步得到)對(duì)應(yīng)的線程為task1,該線程處于RUNNABLE狀態(tài),且下在執(zhí)行HighCPUCase.java中的第17行,而線程task2和task3都因?yàn)榈却i而處于BLOCKED狀態(tài)。仔細(xì)觀察可以發(fā)現(xiàn)task2和task3等待的鎖與task1獲得的鎖完全相同。

    ? 由此我們下結(jié)論,CPU高的原因是因?yàn)榫€程task1,而它正在執(zhí)行HighCPUCase.java中的第17行,線程task2和task3處于阻塞狀態(tài)是因?yàn)閠ask1獲得唯一一把鎖后一直沒(méi)有釋放。然后我們根據(jù)結(jié)論再去看代碼,分析得完全沒(méi)毛病。

實(shí)例2: 利用線程快照分析死鎖

下面為演示死鎖的代碼,模擬有兩個(gè)線程A,B, 兩把鎖1,2,當(dāng)線程1成功拿到鎖1嘗試去拿鎖2發(fā)現(xiàn)拿不到,而此時(shí)線程2成功拿到鎖2嘗試去拿鎖1也拿不到,誰(shuí)也不讓誰(shuí)就只能干耗著了,導(dǎo)致程序一直不能結(jié)束。

public class DeadLockCase {
    public static void main(String[] args){
        Object o1 = new Object();
        Object o2 = new Object();
        new Thread(new SyncThread(o1, o2),  "t1").start();
        new Thread(new SyncThread(o2, o1),  "t2").start();
    }

    static class SyncThread implements Runnable {
        private Object lock1;
        private Object lock2;

        public SyncThread(Object o1, Object o2){
            this.lock1 = o1;
            this.lock2 = o2;
        }

        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name + " acquiring lock on " + lock1);
            synchronized (lock1) {
                System.out.println(name + " acquired lock on " + lock1);
                work();
                System.out.println(name + " acquiring lock on " + lock2);
                synchronized (lock2) {
                    System.out.println(name + " acquired lock on " + lock2);
                    work();
                }
                System.out.println(name + " released lock on " + lock2);
            }
            System.out.println(name + " released lock on " + lock1);
        }

        private void work() {
            try {
                //模擬死鎖的關(guān)鍵,保證線程1只能獲取一個(gè)鎖,而線程2能獲取到另一個(gè)鎖
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

運(yùn)行上面代碼輸出如下,可以看到符合我們的預(yù)期:

image-20181110235104076

下面還是通過(guò)線程快照來(lái)反推:

  1. 首先拿到進(jìn)程id

    image-20181110235201192
  2. 查看進(jìn)程內(nèi)的線程情況

    image-20181110235336030

    可以看到所有線程無(wú)異常指標(biāo),這一步還不知道哪一個(gè)線程有問(wèn)題。

    23152對(duì)就的十六進(jìn)制數(shù)為5a70, 23144對(duì)應(yīng)的十六進(jìn)制數(shù)為5a68,接下來(lái)就重點(diǎn)關(guān)注這兩個(gè)。

  3. 查看線程快照

    開(kāi)始部分:細(xì)心觀察發(fā)現(xiàn)兩個(gè)線程處于BLOCKED狀態(tài)是因?yàn)樵诘认嗷ブg的鎖。

    image-20181111000447280

    結(jié)尾部分:很明確的告訴了你發(fā)現(xiàn)了一個(gè)java級(jí)別死鎖,t2在待t1手上的鎖,t1在等t2手上的鎖。

在實(shí)際生產(chǎn)中肯定比這本文中涉及到的兩個(gè)例子復(fù)雜,但如果能先把基礎(chǔ)的學(xué)會(huì),碰到問(wèn)題也不會(huì)慌而是學(xué)會(huì)去分解復(fù)雜的問(wèn)題,大而化小,最后方能解決問(wèn)題。

參考文章:

如何使用jstack分析線程狀態(tài)

Java死鎖范例以及如何分析死鎖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • GCD全稱Grand Central Dispatch,從名稱可以看出GCD就是起到中央調(diào)度的作用。這個(gè)調(diào)度作用就...
    _小沫閱讀 1,095評(píng)論 1 8
  • 一、top(Linux命令) 執(zhí)行top命令: (查看進(jìn)程15477的詳細(xì)情況,下文用到) 系統(tǒng)信息(前五行): ...
    java菜閱讀 1,209評(píng)論 0 1
  • 《你是最好的自己》是張皓宸和楊楊共同創(chuàng)作的一本小說(shuō)。 書(shū)中有張皓宸寫的21個(gè)關(guān)于身邊人的勵(lì)志故事,有楊楊游歷世界各...
    愛(ài)碎碎念的小梁閱讀 648評(píng)論 0 1
  • 前些日,我以小綠等人的遭遇寫了一篇小說(shuō),投給了《新小說(shuō)》雜志,今天我收到了那家雜志社寄來(lái)的那篇小說(shuō)《小綠》的稿酬,...
    江蘇阿康閱讀 457評(píng)論 2 5
  • 閃亮的每一天閱讀 142評(píng)論 0 0

友情鏈接更多精彩內(nèi)容