【Arthas】Arthas thread查看线程信息

x33g5p2x  于2022-04-06 转载在 其他  
字(11.0k)|赞(0)|评价(0)|浏览(277)

1.概述

转载:Arthas thread查看线程信息

2.开篇

Arthas提供thread命令获取当前thread的信息,包括查询指定最忙的前N个线程并打印堆栈,找出当前阻塞其他线程的线程,显示所有匹配的线程等。

线程相关信息的获取是通过ThreadMXBean来进行获取。

3.ThreadMXBean的API

ThreadMXBean是Java 虚拟机线程系统的管理接口。Java 虚拟机具有此接口的实现类的单一实例。

实现此接口的实例是一个 MXBean,可以通过调用ManagementFactory的getThreadMXBean() 方法或从平台 MBeanServer 方法获得它。

public interface ThreadMXBean extends PlatformManagedObject {
    // 返回活动线程的当前数目,包括守护线程和非守护线程。
    public int getThreadCount();

    // 返回自从 Java 虚拟机启动或峰值重置以来峰值活动线程计数。
    public int getPeakThreadCount();

    // 返回自从 Java 虚拟机启动以来创建和启动的线程总数目。 
    public long getTotalStartedThreadCount();

    // 返回活动守护线程的当前数目。
    public int getDaemonThreadCount();

    // 返回活动线程 ID。在返回的数组中包含的某些线程可能在此方法返回时已经终止。
    public long[] getAllThreadIds();

    // 返回指定 id 的不具有堆栈跟踪的线程的线程信息。
    public ThreadInfo getThreadInfo(long id);

    // 返回其 ID 在输出数组 ids 中的每个线程的线程信息,这些线程不具有堆栈跟踪
    public ThreadInfo[] getThreadInfo(long[] ids);

    // 返回指定 id 的线程的线程信息,并带有指定堆栈追踪元素数的堆栈追踪。
    public ThreadInfo getThreadInfo(long id, int maxDepth);

    // 回其 ID 在输入数组 ids 中的每个线程的线程信息,并带有指定堆栈追踪元素数的堆栈追踪。
    public ThreadInfo[] getThreadInfo(long[] ids, int maxDepth);

    // 测试 Java 虚拟机是否支持线程争用监视。
    public boolean isThreadContentionMonitoringSupported();

    // 测试是否启用了线程争用监视。
    public boolean isThreadContentionMonitoringEnabled();

    // 启用或禁用线程争用监视。默认情况下,线程争用监视是被禁用的。
    public void setThreadContentionMonitoringEnabled(boolean enable);

    // 返回当前线程的总 CPU 时间(以毫微秒为单位)。
    public long getCurrentThreadCpuTime();

    // 返回当前线程在用户模式中执行的 CPU 时间(以毫微秒为单位)。
    public long getCurrentThreadUserTime();

    // 返回指定 ID 的线程的总 CPU 时间(以毫微秒为单位)。
    public long getThreadCpuTime(long id);

    // 返回指定 ID 的线程在用户模式中执行的 CPU 时间(以毫微秒为单位)。
    public long getThreadUserTime(long id);

    // 测试 Java 虚拟机实现是否支持任何线程的 CPU 时间测量。
    public boolean isThreadCpuTimeSupported();

    // 测试 Java 虚拟机是否支持当前线程的 CPU 时间测量。
    public boolean isCurrentThreadCpuTimeSupported();

    // 测试是否启用了线程 CPU 时间测量。
    public boolean isThreadCpuTimeEnabled();

    // 启用或禁用线程 CPU 时间测量。此默认值与平台有关。
    public void setThreadCpuTimeEnabled(boolean enable);

    // 找到处于死锁状态(等待获取对象监视器)的线程的周期。
    public long[] findMonitorDeadlockedThreads();

    // 将峰值线程计数重置为当前活动线程的数量。
    public void resetPeakThreadCount();

    // 查找因为等待获得对象监视器或可拥有同步器而处于死锁状态的线程循环。
    public long[] findDeadlockedThreads();

    // 测试 Java 虚拟机是否支持使用对象监视器的监视。
    public boolean isObjectMonitorUsageSupported();

    // 测试 Java 虚拟机是否支持使用 可拥有同步器的监视。
    public boolean isSynchronizerUsageSupported();

    // 返回每个线程的线程信息,线程 ID 位于输入数组 ids 中,带有堆栈跟踪和同步信息。
    public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers);

    // 返回所有活动线程的线程信息,并带有堆栈跟踪和同步信息。
    public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers);
}

4.源码解析

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();
    }

通过threadMXBean的getThreadInfo获取指定线程id的线程信息。

public static BlockingLockInfo findMostBlockingLock() {
    // 通过threadMXBean.dumpAllThreads返回所有活动线程的线程信息
    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;
}

找出当前阻塞其他线程的线程,通过线程的LockInfo信息来统计。
通过threadMXBean.dumpAllThreads返回所有活动线程的线程信息。

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;
}

通过当前线程所属的线程group来找到线程group的根。
通过ThreadGroup的enumerate来获取当前所有的线程。

private ExitStatus processTopBusyThreads(CommandProcess process) {
    ThreadSampler threadSampler = new ThreadSampler();
    // 进行第一次采样
    threadSampler.sample(ThreadUtil.getThreads());
    // 等待一段时间的
    threadSampler.pause(sampleInterval);
    // 进行第二次采样
    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();
}

通过两次采样来计算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;
    }
}

通过threadMXBean.getThreadCpuTime获取cpu消耗进行据算。

相关文章