使用
导出线程堆栈:jstack pid > c:/file.txt
线程状态
Java官方定义的线程状态有6种,定义在一个枚举类中:
NEW,未启动的线程,不会出现在Dump中;
RUNNABLE,运行中,包括就绪状态和运行中状态, 调用start方法并获取到锁后进入就绪状态,获取到CPU后进入运行中状态;(当线程遇到I/O或者调用suspend挂起线程时还是在运行状态);
BLOCKED,受阻塞并等待监视器锁,被某个锁(synchronizers)給block住了;
WATING,无限期等待某个condition或monitor发生,一般停留在park, wait, sleep,join (未设置超时时间)等语句里;
TIMED_WATING,有时限的等待另一个线程的特定操作,如sleep,加上超时时间的wait;
TERMINATED,已退出的。
堆栈信息中不存在NEW和TERMINATED状态。
监视器(Monitor)
Monitor是用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。
Monitor可以类比为一个特殊的房间,这个房间中有一些被保护的数据,Monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据,进入房间即为持有Monitor,退出房间即为释放Monitor。
当一个线程需要访问受保护的数据(即需要获取对象的Monitor)时,它会首先在entry-set入口队列中排队(这里并不是真正的按照排队顺序),如果没有其他线程正在持有对象的Monitor,那么它会和entry-set队列和wait-set队列中的被唤醒的其他线程进行竞争(即通过CPU调度),选出一个线程来获取对象的Monitor,执行受保护的代码段,执行完毕后释放Monitor,如果已经有线程持有对象的Monitor,那么需要等待其释放Monitor后再进行竞争。
再说一下wait-set队列。当一个线程拥有Monitor后,经过某些条件的判断(比如用户取钱发现账户没钱),这个时候需要调用Object的wait方法,线程就释放了Monitor,进入wait-set队列,等待Object的notify方法(比如用户向账户里面存钱)。当该对象调用了notify方法或者notifyAll方法后,wait-set中的线程就会被唤醒,然后在wait-set队列中被唤醒的线程和entry-set队列中的线程一起通过CPU调度来竞争对象的Monitor,最终只有一个线程能获取对象的Monitor。
进入区(Entry Set):表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则进入拥有者,否则在进入区等待;
拥有者(The Owner):表示某一线程成功竞争到对象锁;
等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒;
一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是“Active Thread”,而其它线程都是“Waiting Thread”,分别在两个队列“ Entry Set”和“Wait Set”里面等候;
在“Entry Set”中等待的线程状态是“Waiting for monitor entry”,而在“Wait Set”中等待的线程状态是“in Object.wait()”;
我们称被 synchronized保护起来的代码段为临界区,当一个线程申请进入临界区时,它就进入了 “Entry Set”队列;
线程堆栈解释
线程名称:若未设置,自动命名"thread-x" 或 "线程池名称-thread-x",线程池若未设置,自动命名"pool-x",若是守护线程后面会显示“daemon”;
线程优先级:最低1,最高10,默认5,优先级高的不一定先执行;
nid:tid映射的操作系统中的线程id,这里是用16进制的表示;
线程动作(本地线程状态):线程状态产生的原因
---runnable:此时线程状态一般为RUNNABLE;
---in Object.wait():等待区等待,此时线程状态为WAITING或TIMED_WAITING;
---waiting for monitor entry:进入区等待,等待进入一个临界区,所以它在”Entry Set“队列中等待,线程状态一般是 BLOCKED;
---waiting on condition:等待区等待、被park,等待另一个条件的发生来把自己唤醒,线程状态是:WAITING(parking,一直等那个条件发生)或 TIMED_WAITING (parking或sleeping,定时的,那个条件不到来,也将定时唤醒自己);
---sleeping:休眠的线程,调用了Thread.sleep();
调用修饰:表示线程在方法调用时,额外的重要的操作
---locked <地址/锁ID> 目标:使用synchronized申请对象锁成功,监视器的拥有者;
---waiting to lock <地址/锁ID> 目标:使用synchronized申请对象锁未成功,在进入区等待;
---waiting on <地址/锁ID> 目标:使用synchronized申请对象锁成功后,释放锁在等待区等待;
---parking to wait for <地址/锁ID> 目标:park是基本的线程阻塞原语,不通过监视器在对象上阻塞,与synchronized体系不同;
尖括号中表示锁ID,这个是系统自动产生的,我们只需要知道每次打印的堆栈,同一个ID表示是同一个锁即可。
---当一个线程占有一个锁的时候,线程的堆栈中会打印--locked<0x00000000d77d50c8>
---当一个线程正在等待其它线程释放该锁,线程堆栈中会打印--waiting to lock<0x00000000d77d50c8>
---当一个线程占有一个锁,但又执行到该锁的wait()方法上,线程堆栈中首先打印locked,然后又会打印--waiting on <0x00000000d77d50c8>
问题分析
大量线程在waiting for monitor entry
可能是一个全局锁阻塞住了大量线程,随着时间流逝,waiting for monitor entry 的线程越来越多,没有减少的趋势,可能意味着某些线程在临界区里呆的时间太长了,以至于越来越多新线程迟迟无法进入临界区;
大量线程在waiting on condition
可能是它们又跑去获取第三方资源,尤其是第三方网络资源,迟迟获取不到Response,导致大量线程进入等待状态,所以如果你发现有大量的线程都处在 Wait on condition,从线程堆栈看,正等待网络读写,这可能是一个网络瓶颈的征兆,因为网络阻塞导致线程无法执行;
线程在in Object.wait()
当线程获得了 Monitor,如果发现线程继续运行的条件没有满足,它则调用对象的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列,一般都是RMI相关线程(RMI RenewClean、 GC Daemon、RMI Reaper),GC线程(Finalizer),引用对象垃圾回收线程(Reference Handler)等系统线程处于这种状态;
线程处于parking to wait for <> (a java.util.concurrent.SynchronousQueue$TransferStack)
首先,本线程肯定是在等待某个条件的发生,来把自己唤醒。其次,SynchronousQueue 并不是一个队列,只是线程之间移交信息的机制,当我们把一个元素放入到 SynchronousQueue 中时必须有另一个线程正在等待接受移交的任务,因此这就是本线程在等待的条件。
CPU占用率很高,响应很慢
先找到占用CPU的进程,然后再定位到对应的线程,最后分析出对应的堆栈信息。
Linux环境下,使用 top -Hp pid 可以查看进程下各个线程的cpu 内存情况,使用printf “%x\n” <threadid> 可将线程ID转成16进制(即上面的nid);
Windows环境下,没有自带命令,可以使用Process Explorer工具查看;
CPU占用率不高,但响应很慢
在整个请求的过程中多次执行Thread Dump然后进行对比,取得 BLOCKED 状态的线程列表,通常是因为线程停在了I/O、数据库连接或网络连接的地方。
在线分析
使用jca工具分析