天天看点

jstack问题定位分析

jstack

jstack

java虚拟机

自带的一种

堆栈跟踪工具

jstack

用于打印出给定的

java进程ID

core file

远程调试服务

的Java堆栈信息。

jstack主要用于生成java虚拟机当前时刻的线程快照,线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。

线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

top命令

在linux环境下,可以通过

top

命令查看各个进程的cpu使用情况,默认按cpu使用率排序。

jstack命令

通过top命令定位到cpu占用率较高的线程之后,继续使用

jstack pid

命令查看当前java进程的堆栈状态。

jstack命令生成的thread dump信息包含了JVM中所有存活的线程,为了分析指定线程,必须找出对应线程的调用栈,应该如何找?

在top命令中,已经获取到了占用cpu资源较高的线程pid,将该pid转成16进制的值,在thread dump中每个线程都有一个nid,找到对应的nid即可;隔段时间再执行一次stack命令获取thread dump,区分两份dump是否有差别。

线程5种状态

新建状态(New) 新创建了一个线程对象。

就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

运行状态(Running) 就绪状态的线程获取了CPU,执行程序代码。

阻塞状态(Blocked) 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

  • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

通过thread dump分析线程状态

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

在dump中,线程一般存在如下几种状态:

  • RUNNABLE,线程运行中或I/O等待
  • BLOCKED,线程被阻塞,在等待monitor锁(synchronized关键字)
  • TIMED_WAITING 线程在等待唤醒,但设置了时限
  • WAITING 线程在无限等待唤醒

log4j 1.X版本引发线程blocked死锁问题(synchronized同步锁)

jstack问题定位分析

线程处于BLOCK状态。

  • 该线程是blocked在了log4j.Category.callAppenders上,显然可以发现这是个log4j的问题。
  • 解决方法:使用log4j2或者使用slf4j替代直接使用log4j

wait挂起线程

jstack问题定位分析

线程1和2都处于WAITING状态

  • 线程1和2都是先

    locked <0x000000076bf62500>

    ,再

    waiting on <0x000000076bf62500>

    ,之所以先锁再等同一个对象,是因为wait方法需要先通过synchronized获得该地址对象的monitor;
  • waiting on <0x000000076bf62500>

    说明线程执行了wait方法之后,释放了monitor,进入到"Wait Set"队列,等待其它线程执行地址为0x000000076bf62500对象的notify方法,并唤醒自己 

连接池

jstack问题定位分析

线程处于WAITING状态

  • 多执行几次jstack可以发现大约有部分的线程处于waitting状态,该状态表明该线程正在执行obj.wait()方法,放弃了 Monitor,进入 “Wait Set”队列,为什么阻塞住呢,继续往下看堆栈信息,可以看到该线程正在做borrowobject操作,可以大概看到这里是一个数据库连接池的相关操作,可以适当调整数据库连接池大小