Arthas thread查看線程信息

系列

開(kāi)篇

  • Arthas提供thread命令獲取當(dāng)前thread的信息套啤,包括查詢(xún)指定最忙的前N個(gè)線程并打印堆棧,找出當(dāng)前阻塞其他線程的線程,顯示所有匹配的線程等棵磷。
  • 線程相關(guān)信息的獲取是通過(guò)ThreadMXBean來(lái)進(jìn)行獲取太防。


ThreadMXBean的API

  • ThreadMXBean是Java 虛擬機(jī)線程系統(tǒng)的管理接口谈撒。Java 虛擬機(jī)具有此接口的實(shí)現(xiàn)類(lèi)的單一實(shí)例蚕捉。
  • 實(shí)現(xiàn)此接口的實(shí)例是一個(gè) MXBean律胀,可以通過(guò)調(diào)用ManagementFactory的getThreadMXBean() 方法或從平臺(tái) MBeanServer 方法獲得它贝乎。

public interface ThreadMXBean extends PlatformManagedObject {
    // 返回活動(dòng)線程的當(dāng)前數(shù)目情连,包括守護(hù)線程和非守護(hù)線程。
    public int getThreadCount();

    // 返回自從 Java 虛擬機(jī)啟動(dòng)或峰值重置以來(lái)峰值活動(dòng)線程計(jì)數(shù)览效。
    public int getPeakThreadCount();

    // 返回自從 Java 虛擬機(jī)啟動(dòng)以來(lái)創(chuàng)建和啟動(dòng)的線程總數(shù)目却舀。 
    public long getTotalStartedThreadCount();

    // 返回活動(dòng)守護(hù)線程的當(dāng)前數(shù)目虫几。
    public int getDaemonThreadCount();

    // 返回活動(dòng)線程 ID。在返回的數(shù)組中包含的某些線程可能在此方法返回時(shí)已經(jīng)終止挽拔。
    public long[] getAllThreadIds();

    // 返回指定 id 的不具有堆棧跟蹤的線程的線程信息辆脸。
    public ThreadInfo getThreadInfo(long id);

    // 返回其 ID 在輸出數(shù)組 ids 中的每個(gè)線程的線程信息,這些線程不具有堆棧跟蹤
    public ThreadInfo[] getThreadInfo(long[] ids);

    // 返回指定 id 的線程的線程信息螃诅,并帶有指定堆棧追蹤元素?cái)?shù)的堆棧追蹤啡氢。
    public ThreadInfo getThreadInfo(long id, int maxDepth);

    // 回其 ID 在輸入數(shù)組 ids 中的每個(gè)線程的線程信息,并帶有指定堆棧追蹤元素?cái)?shù)的堆棧追蹤术裸。
    public ThreadInfo[] getThreadInfo(long[] ids, int maxDepth);

    // 測(cè)試 Java 虛擬機(jī)是否支持線程爭(zhēng)用監(jiān)視倘是。
    public boolean isThreadContentionMonitoringSupported();

    // 測(cè)試是否啟用了線程爭(zhēng)用監(jiān)視。
    public boolean isThreadContentionMonitoringEnabled();

    // 啟用或禁用線程爭(zhēng)用監(jiān)視袭艺。默認(rèn)情況下搀崭,線程爭(zhēng)用監(jiān)視是被禁用的。
    public void setThreadContentionMonitoringEnabled(boolean enable);

    // 返回當(dāng)前線程的總 CPU 時(shí)間(以毫微秒為單位)猾编。
    public long getCurrentThreadCpuTime();

    // 返回當(dāng)前線程在用戶(hù)模式中執(zhí)行的 CPU 時(shí)間(以毫微秒為單位)瘤睹。
    public long getCurrentThreadUserTime();

    // 返回指定 ID 的線程的總 CPU 時(shí)間(以毫微秒為單位)。
    public long getThreadCpuTime(long id);

    // 返回指定 ID 的線程在用戶(hù)模式中執(zhí)行的 CPU 時(shí)間(以毫微秒為單位)袍镀。
    public long getThreadUserTime(long id);

    // 測(cè)試 Java 虛擬機(jī)實(shí)現(xiàn)是否支持任何線程的 CPU 時(shí)間測(cè)量默蚌。
    public boolean isThreadCpuTimeSupported();

    // 測(cè)試 Java 虛擬機(jī)是否支持當(dāng)前線程的 CPU 時(shí)間測(cè)量。
    public boolean isCurrentThreadCpuTimeSupported();

    // 測(cè)試是否啟用了線程 CPU 時(shí)間測(cè)量苇羡。
    public boolean isThreadCpuTimeEnabled();

    // 啟用或禁用線程 CPU 時(shí)間測(cè)量绸吸。此默認(rèn)值與平臺(tái)有關(guān)。
    public void setThreadCpuTimeEnabled(boolean enable);

    // 找到處于死鎖狀態(tài)(等待獲取對(duì)象監(jiān)視器)的線程的周期设江。
    public long[] findMonitorDeadlockedThreads();

    // 將峰值線程計(jì)數(shù)重置為當(dāng)前活動(dòng)線程的數(shù)量锦茁。
    public void resetPeakThreadCount();

    // 查找因?yàn)榈却@得對(duì)象監(jiān)視器或可擁有同步器而處于死鎖狀態(tài)的線程循環(huán)。
    public long[] findDeadlockedThreads();

    // 測(cè)試 Java 虛擬機(jī)是否支持使用對(duì)象監(jiān)視器的監(jiān)視叉存。
    public boolean isObjectMonitorUsageSupported();

    // 測(cè)試 Java 虛擬機(jī)是否支持使用 可擁有同步器的監(jiān)視码俩。
    public boolean isSynchronizerUsageSupported();

    // 返回每個(gè)線程的線程信息,線程 ID 位于輸入數(shù)組 ids 中歼捏,帶有堆棧跟蹤和同步信息稿存。
    public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers);

    // 返回所有活動(dòng)線程的線程信息,并帶有堆棧跟蹤和同步信息瞳秽。
    public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers);
}


源碼解析

public class ThreadCommand extends AnnotatedCommand {
    private static Set<String> states = null;
    private static ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

    public void process(CommandProcess process) {
        ExitStatus exitStatus;
        if (id > 0) {
            // 獲取指定的id的線程
            exitStatus = processThread(process);
        } else if (topNBusy != null) {
            // 獲取topN的busy的線程
            exitStatus = processTopBusyThreads(process);
        } else if (findMostBlockingThread) {
            // 獲取阻塞的線程
            exitStatus = processBlockingThread(process);
        } else {
            // 獲取全部的線程
            exitStatus = processAllThreads(process);
        }
        CommandUtils.end(process, exitStatus);
    }
}
  • 支持獲取指定線程信息瓣履、topN的busy線程、阻塞線程练俐、全部線程信息等袖迎。
  private ExitStatus processThread(CommandProcess process) {
        // 獲取線程的指定信息
        ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(new long[]{id}, lockedMonitors, lockedSynchronizers);
        if (threadInfos == null || threadInfos.length < 1 || threadInfos[0] == null) {
            return ExitStatus.failure(1, "thread do not exist! id: " + id);
        }

        process.appendResult(new ThreadModel(threadInfos[0]));
        return ExitStatus.success();
    }
  • 通過(guò)threadMXBean的getThreadInfo獲取指定線程id的線程信息。
    public static BlockingLockInfo findMostBlockingLock() {
        // 通過(guò)threadMXBean.dumpAllThreads返回所有活動(dòng)線程的線程信息
        ThreadInfo[] infos = threadMXBean.dumpAllThreads(threadMXBean.isObjectMonitorUsageSupported(),
                threadMXBean.isSynchronizerUsageSupported());

        // a map of <LockInfo.getIdentityHashCode, number of thread blocking on this>
        Map<Integer, Integer> blockCountPerLock = new HashMap<Integer, Integer>();
        // a map of <LockInfo.getIdentityHashCode, the thread info that holding this lock
        Map<Integer, ThreadInfo> ownerThreadPerLock = new HashMap<Integer, ThreadInfo>();

        for (ThreadInfo info: infos) {
            if (info == null) {
                continue;
            }

            LockInfo lockInfo = info.getLockInfo();
            if (lockInfo != null) {
                // the current thread is blocked waiting on some condition
                if (blockCountPerLock.get(lockInfo.getIdentityHashCode()) == null) {
                    blockCountPerLock.put(lockInfo.getIdentityHashCode(), 0);
                }
                int blockedCount = blockCountPerLock.get(lockInfo.getIdentityHashCode());
                blockCountPerLock.put(lockInfo.getIdentityHashCode(), blockedCount + 1);
            }

            for (MonitorInfo monitorInfo: info.getLockedMonitors()) {
                // the object monitor currently held by this thread
                if (ownerThreadPerLock.get(monitorInfo.getIdentityHashCode()) == null) {
                    ownerThreadPerLock.put(monitorInfo.getIdentityHashCode(), info);
                }
            }

            for (LockInfo lockedSync: info.getLockedSynchronizers()) {
                // the ownable synchronizer currently held by this thread
                if (ownerThreadPerLock.get(lockedSync.getIdentityHashCode()) == null) {
                    ownerThreadPerLock.put(lockedSync.getIdentityHashCode(), info);
                }
            }
        }

        // find the thread that is holding the lock that blocking the largest number of threads.
        int mostBlockingLock = 0; // System.identityHashCode(null) == 0
        int maxBlockingCount = 0;
        for (Map.Entry<Integer, Integer> entry: blockCountPerLock.entrySet()) {
            if (entry.getValue() > maxBlockingCount && ownerThreadPerLock.get(entry.getKey()) != null) {
                // the lock is explicitly held by anther thread.
                maxBlockingCount = entry.getValue();
                mostBlockingLock = entry.getKey();
            }
        }

        if (mostBlockingLock == 0) {
            // nothing found
            return EMPTY_INFO;
        }

        BlockingLockInfo blockingLockInfo = new BlockingLockInfo();
        blockingLockInfo.setThreadInfo(ownerThreadPerLock.get(mostBlockingLock));
        blockingLockInfo.setLockIdentityHashCode(mostBlockingLock);
        blockingLockInfo.setBlockingThreadCount(blockCountPerLock.get(mostBlockingLock));
        return blockingLockInfo;
    }
  • 找出當(dāng)前阻塞其他線程的線程,通過(guò)線程的LockInfo信息來(lái)統(tǒng)計(jì)燕锥。
  • 通過(guò)threadMXBean.dumpAllThreads返回所有活動(dòng)線程的線程信息辜贵。
    public static ThreadGroup getRoot() {
        ThreadGroup group = Thread.currentThread().getThreadGroup();
        ThreadGroup parent;
        while ((parent = group.getParent()) != null) {
            group = parent;
        }
        return group;
    }


    public static List<ThreadVO> getThreads() {
        ThreadGroup root = getRoot();
        Thread[] threads = new Thread[root.activeCount()];
        while (root.enumerate(threads, true) == threads.length) {
            threads = new Thread[threads.length * 2];
        }
        List<ThreadVO> list = new ArrayList<ThreadVO>(threads.length);
        for (Thread thread : threads) {
            if (thread != null) {
                ThreadVO threadVO = createThreadVO(thread);
                list.add(threadVO);
            }
        }
        return list;
    }
  • 通過(guò)當(dāng)前線程所屬的線程group來(lái)找到線程group的根。
  • 通過(guò)ThreadGroup的enumerate來(lái)獲取當(dāng)前所有的線程归形。
    private ExitStatus processTopBusyThreads(CommandProcess process) {
        ThreadSampler threadSampler = new ThreadSampler();
        // 進(jìn)行第一次采樣
        threadSampler.sample(ThreadUtil.getThreads());
        // 等待一段時(shí)間的
        threadSampler.pause(sampleInterval);
        // 進(jìn)行第二次采樣
        List<ThreadVO> threadStats = threadSampler.sample(ThreadUtil.getThreads());

        int limit = Math.min(threadStats.size(), topNBusy);
        List<ThreadVO> topNThreads = threadStats.subList(0, limit);
        List<Long> tids = new ArrayList<Long>(topNThreads.size());
        for (ThreadVO thread : topNThreads) {
            if (thread.getId() > 0) {
                tids.add(thread.getId());
            }
        }
        // 獲取線程信息
        ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(ArrayUtils.toPrimitive(tids.toArray(new Long[0])), lockedMonitors, lockedSynchronizers);
        if (tids.size()> 0 && threadInfos == null) {
            return ExitStatus.failure(1, "get top busy threads failed");
        }

        //threadInfo with cpuUsage
        List<BusyThreadInfo> busyThreadInfos = new ArrayList<BusyThreadInfo>(topNThreads.size());
        for (ThreadVO thread : topNThreads) {
            ThreadInfo threadInfo = findThreadInfoById(threadInfos, thread.getId());
            BusyThreadInfo busyThread = new BusyThreadInfo(thread, threadInfo);
            busyThreadInfos.add(busyThread);
        }
        process.appendResult(new ThreadModel(busyThreadInfos));
        return ExitStatus.success();
    }
  • 通過(guò)兩次采樣來(lái)計(jì)算cpu消耗最高的線程池托慨。
public class ThreadSampler {

    private static ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    private static HotspotThreadMBean hotspotThreadMBean;
    private static boolean hotspotThreadMBeanEnable = true;

    private Map<ThreadVO, Long> lastCpuTimes = new HashMap<ThreadVO, Long>();

    private long lastSampleTimeNanos;
    private boolean includeInternalThreads = true;


    public List<ThreadVO> sample(Collection<ThreadVO> originThreads) {

        List<ThreadVO> threads = new ArrayList<ThreadVO>(originThreads);

        // Sample CPU
        if (lastCpuTimes.isEmpty()) {
            lastSampleTimeNanos = System.nanoTime();
            for (ThreadVO thread : threads) {
                if (thread.getId() > 0) {
                    long cpu = threadMXBean.getThreadCpuTime(thread.getId());
                    lastCpuTimes.put(thread, cpu);
                    thread.setTime(cpu / 1000000);
                }
            }

            // add internal threads
            Map<String, Long> internalThreadCpuTimes = getInternalThreadCpuTimes();
            if (internalThreadCpuTimes != null) {
                for (Map.Entry<String, Long> entry : internalThreadCpuTimes.entrySet()) {
                    String key = entry.getKey();
                    ThreadVO thread = createThreadVO(key);
                    thread.setTime(entry.getValue() / 1000000);
                    threads.add(thread);
                    lastCpuTimes.put(thread, entry.getValue());
                }
            }

            //sort by time
            Collections.sort(threads, new Comparator<ThreadVO>() {
                @Override
                public int compare(ThreadVO o1, ThreadVO o2) {
                    long l1 = o1.getTime();
                    long l2 = o2.getTime();
                    if (l1 < l2) {
                        return 1;
                    } else if (l1 > l2) {
                        return -1;
                    } else {
                        return 0;
                    }
                }
            });
            return threads;
        }

        // Resample
        long newSampleTimeNanos = System.nanoTime();
        Map<ThreadVO, Long> newCpuTimes = new HashMap<ThreadVO, Long>(threads.size());
        for (ThreadVO thread : threads) {
            if (thread.getId() > 0) {
                long cpu = threadMXBean.getThreadCpuTime(thread.getId());
                newCpuTimes.put(thread, cpu);
            }
        }
        // internal threads
        Map<String, Long> newInternalThreadCpuTimes = getInternalThreadCpuTimes();
        if (newInternalThreadCpuTimes != null) {
            for (Map.Entry<String, Long> entry : newInternalThreadCpuTimes.entrySet()) {
                ThreadVO threadVO = createThreadVO(entry.getKey());
                threads.add(threadVO);
                newCpuTimes.put(threadVO, entry.getValue());
            }
        }

        // Compute delta time
        final Map<ThreadVO, Long> deltas = new HashMap<ThreadVO, Long>(threads.size());
        for (ThreadVO thread : newCpuTimes.keySet()) {
            Long t = lastCpuTimes.get(thread);
            if (t == null) {
                t = 0L;
            }
            long time1 = t;
            long time2 = newCpuTimes.get(thread);
            if (time1 == -1) {
                time1 = time2;
            } else if (time2 == -1) {
                time2 = time1;
            }
            long delta = time2 - time1;
            deltas.put(thread, delta);
        }

        long sampleIntervalNanos = newSampleTimeNanos - lastSampleTimeNanos;

        // Compute cpu usage
        final HashMap<ThreadVO, Double> cpuUsages = new HashMap<ThreadVO, Double>(threads.size());
        for (ThreadVO thread : threads) {
            double cpu = sampleIntervalNanos == 0 ? 0 : (deltas.get(thread) * 10000 / sampleIntervalNanos / 100.0);
            cpuUsages.put(thread, cpu);
        }

        // Sort by CPU time : should be a rendering hint...
        Collections.sort(threads, new Comparator<ThreadVO>() {
            public int compare(ThreadVO o1, ThreadVO o2) {
                long l1 = deltas.get(o1);
                long l2 = deltas.get(o2);
                if (l1 < l2) {
                    return 1;
                } else if (l1 > l2) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });

        for (ThreadVO thread : threads) {
            //nanos to mills
            long timeMills = newCpuTimes.get(thread) / 1000000;
            long deltaTime = deltas.get(thread) / 1000000;
            double cpu = cpuUsages.get(thread);

            thread.setCpu(cpu);
            thread.setTime(timeMills);
            thread.setDeltaTime(deltaTime);
        }
        lastCpuTimes = newCpuTimes;
        lastSampleTimeNanos = newSampleTimeNanos;

        return threads;
    }

    private Map<String, Long> getInternalThreadCpuTimes() {
        if (hotspotThreadMBeanEnable && includeInternalThreads) {
            try {
                if (hotspotThreadMBean == null) {
                    hotspotThreadMBean = ManagementFactoryHelper.getHotspotThreadMBean();
                }
                return hotspotThreadMBean.getInternalThreadCpuTimes();
            } catch (Throwable e) {
                //ignore ex
                hotspotThreadMBeanEnable = false;
            }
        }
        return null;
    }
}
  • 通過(guò)threadMXBean.getThreadCpuTime獲取cpu消耗進(jìn)行據(jù)算。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末连霉,一起剝皮案震驚了整個(gè)濱河市榴芳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跺撼,老刑警劉巖窟感,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異歉井,居然都是意外死亡柿祈,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)哩至,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)躏嚎,“玉大人,你說(shuō)我怎么就攤上這事菩貌÷叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵箭阶,是天一觀的道長(zhǎng)虚茶。 經(jīng)常有香客問(wèn)我,道長(zhǎng)仇参,這世上最難降的妖魔是什么嘹叫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮诈乒,結(jié)果婚禮上罩扇,老公的妹妹穿的比我還像新娘。我一直安慰自己怕磨,他們只是感情好喂饥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著肠鲫,像睡著了一般仰泻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滩届,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼帜消。 笑死棠枉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泡挺。 我是一名探鬼主播辈讶,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼娄猫!你這毒婦竟也來(lái)了贱除?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤媳溺,失蹤者是張志新(化名)和其女友劉穎月幌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體悬蔽,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扯躺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝎困。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片录语。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖禾乘,靈堂內(nèi)的尸體忽然破棺而出澎埠,到底是詐尸還是另有隱情,我是刑警寧澤始藕,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布蒲稳,位于F島的核電站,受9級(jí)特大地震影響鳄虱,放射性物質(zhì)發(fā)生泄漏弟塞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一拙已、第九天 我趴在偏房一處隱蔽的房頂上張望决记。 院中可真熱鬧,春花似錦倍踪、人聲如沸系宫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扩借。三九已至,卻和暖如春缤至,著一層夾襖步出監(jiān)牢的瞬間潮罪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嫉到,地道東北人沃暗。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像何恶,于是被迫代替她去往敵國(guó)和親孽锥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容