Silentdoer

导航

如何使用jstack分析线程状态

转载:http://www.jianshu.com/p/6690f7e92f27,做了部分修改优化

背景

记得前段时间,同事说他们测试环境的服务器cpu使用率一直处于100%,本地又没有什么接口调用,为什么会这样?cpu使用率居高不下,自然是有某些线程一直占用着cpu资源,那又如何查看占用cpu较高的线程?


 

当然一个正常的程序员不会写出上述代码,这里只是为了让一个线程占用较高的cpu资源(while循环执行逻辑会一直占用线程导致接近100%的cpu占用(如果是wait()挂起线程则不会),如果是多核如4核则一个应用程序cpu占用达到400%也是可能的)。

top命令

在linux环境下,可以通过top命令查看各个进程的cpu使用情况,默认按cpu使用率排序


 

1、上图中可以看出pid为23344的java进程占用了较多的cpu资源;
2、通过top -Hp 23344可以查看该进程各个线程的cpu使用情况;

(注意, top -Hp 23344后显示如下图,这里的PID则不是进程id了而是进程23344的各个线程id


 

上图中可以看出pid为25077的线程占了较多的cpu资源,利用jstack命令可以继续查看该线程当前的堆栈状态(jstack是通过查看进程详情来查看进程里线程的状态,jstack $进程id)。

jstack命令

通过top命令定位到cpu占用率较高的线程之后,继续使用jstack pid(进程id)命令查看当前java进程的堆栈状态


 

(上面的nid就是对应操作系统的线程id(而tid是java里线程的id,虚拟线程出来之前,java线程和操作系统线程是一对一的关系),不过它是十六进制的;最前面""括起来的是java线程名(如pool-1-thread-1),可以通过上面top -Hp 23344得到的占用较多CPU资源的线程id 25077 将它通过printf "%x\n" 25077转换为十六进制,然后再在jstack -l 23344【进程id】里搜这个线程id的十六进制来找到相关的代码;由下往上看打印信息才是时间顺序)

jstack命令生成的thread dump信息包含了JVM中所有存活的线程,为了分析指定线程,必须找出对应线程的调用栈,应该如何找?(jstack的输出里就有部分调用栈,最上面的是调用栈顶层即业务代码)

在top命令中,已经获取到了占用cpu资源较高的线程pid,将该pid转成16进制的值(通过printf "%x\n" 15来将15转换成16进制值f),在thread dump中每个线程都有一个nid,找到对应的nid即可;隔段时间再执行一次jstack命令获取thread dump,区分两份dump是否有差别,在nid=0x246c的线程调用栈中,发现该线程一直在执行JstackCase类的内部类Task第33行的calculate方法,得到这个信息,就可以检查对应的代码是否有问题。

通过thread dump分析线程状态

除了上述的分析,大多数情况下会基于thead dump分析当前各个线程的运行情况,如是否存在死锁、是否存在一个线程长时间持有锁不放等等。

在dump中,线程一般存在如下几种状态:
1、RUNNABLE,线程处于执行中
2、BLOCKED,线程被阻塞(加锁,且不会有时间限制)
3、WAITING,线程正在等待(wait等待,没有时间限制)

4. TIMED_WAITING,这个一般是指这个线程是Timer定期执行任务,但是当前处于waiting状态(有时间限制的等待,包括sleep、wait(timeout)等)

实例1:多线程竞争synchronized锁

 

很明显:线程1获取到锁,处于RUNNABLE状态,线程2处于BLOCK状态
1、locked <0x000000076bf62208>说明线程1地址为0x000000076bf62208对象进行了加锁
2、waiting to lock <0x000000076bf62208> 说明线程2在等待地址为0x000000076bf62208对象上的锁(这两个线程需要对同一个对象进行加锁);
3、waiting for monitor entry [0x000000001e21f000]说明线程2是通过synchronized关键字进入了监视器的临界区等待,并处于"Entry Set"队列,等待monitor,具体实现可以参考深入分析synchronized的JVM实现

实例2:通过wait挂起线程
static class Task implements Runnable {
    @Override
    public void run() {
        synchronized (lock) {
            try {
                lock.wait();
                //TimeUnit.SECONDS.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
dump结果

 

线程1和2都处于WAITING状态
1、线程1和2都是先locked <0x000000076bf62500>,再waiting on <0x000000076bf62500>,之所以先锁再等同一个对象,是因为wait方法需要先通过synchronized获得该地址对象的monitor;
2、waiting on <0x000000076bf62500>说明线程执行了wait方法之后,释放了monitor,进入到"Wait Set"队列,等待其它线程执行地址为0x000000076bf62500对象的notify方法,并唤醒自己,具体实现可以参考深入分析Object.wait/notify实现机制

posted on 2019-10-25 10:05  Silentdoer  阅读(15199)  评论(0编辑  收藏  举报