什么是线程堆栈
Java线程堆栈是虚拟机中线程(包括锁)状态的一个瞬间快照,即
系统在某个时刻所有线程的运行状态。
包括每一个线程的调用堆栈,锁的持有情况等信息,从线程堆栈中可以得到以下信息:
-
线程的名字,ID,线程的数量等;
-
线程的运行状态,锁的状态(锁被那个线程持有,哪个线程在等待锁等);
-
函数间的调用关系,包括完整类名,所执行的方法,源代码的行数等;
可以通过Jstack获取应用运行时的线程堆栈,可以通过如下方式获取线程堆栈:
jstack pid>>jstack.log
对于Java应用而言,以下常见的几个性能问题都可以从线程堆栈入手定位:
-
系统挂起无响应
-
系统CPU较高
-
系统运行的响应时间长
-
线程死锁等
cpu飙升的原因都有哪些?
1、代码中存在死循环
public String loop() {
boolean b = true;
while (b) {
}
return "123";
}
2、高并发的时候,所有线程都处在运行状态,消耗CPU资源。比如定时任务跑批量,比如外部请求大批量访问等等。建议,接口比较耗时的操作不要写成同步。
3、系统发生了频繁的 Full GC(jstat -gcutil pid)来查看各区gc使用率,特别是老年代fullgc指的是老年代的gc,如果老年代的内存使用率很高,会造成频繁的fullgc。
线程的运行状态
想知道线程是在卖力工作还是偷懒休息,这就需要关注线程的运行状态,常用到的几个线程状态有:RUNNABLE,Running,BLOCKED,WAITING,TIMED_WAITING。
RUNNABLE
Runnable:当调用thread.start()后,线程变成为Runnable状态。只要得到CPU,就可以执行;
Running:线程正在执行;
Running状态代表线程正处于运行状态。一般情况下处于运行状态线程是会消耗CPU的。
但不是所有的RUNNABLE都会消耗CPU,比如线程进行网络IO时,这时线程状态是挂起的,但由于挂起发生在本地代码,虚拟机并不感知,所以不会像显示调用Java的sleep()或者wait()等方法进入WAITING状态,只有等数据到来时才消耗一点CPU.
WATING
执行thread.join()或在锁对象调用obj.wait()等情况就会进该状态,表明线程正处于等待某个资源或条件发生来唤醒自己;
TIMED_WAITING
执行Thread.sleep(long)、thread.join(long)或obj.wait(long)等就会进该状态,与Waiting的区别在于Timed_Waiting的等待有时间限制;
这两种状态表示线程被挂起,等待被唤醒,当设置超时时间时状态为TIMED_WAITING,如果是未设置超时时间,这时的状态为WATING,必须等待lock.notify()或lock.notifyAll()或接收到interrupt信号才能退出等待状态,TIMED_WAITING/WATING下还需要关注下面几个线程状态:
-
waiting on condition:说明线程等待另一个条件的发生,来把自己唤醒;
-
on object monitor: 说明该线程正在执行obj.wait()方法,放弃了 Monitor,进入 “Wait Set”队列;
BLOCKED
如果进入同步方法或同步代码块,没有获取到锁,则会进入该状态;
此时的线程处于阻塞状态,一般是在等待进入一个临界区“waiting for monitor entry”,这种状态是需要重点关注的
哪些线程状态占用CPU?
处于TIMED_WAITING、WATING、BLOCKED状态的线程是不消耗CPU的,而处于Running状态的线程要结合当前线程代码的性质判断是否消耗CPU
-
纯java运算代码,并且未被挂起,是消耗CPU的;
-
网络IO操作,在等待数据时是不消耗CPU的;
对于jstack日志,我们要着重关注如下关键信息
Deadlock:表示有死锁
Waiting on condition:等待某个资源或条件发生来唤醒自己。具体需要结合jstacktrace来分析,比如线程正在sleep,网络读写繁忙而等待
Blocked:阻塞 Waiting on monitor entry:在等待获取锁
in Object.wait():获取锁后又执行obj.wait()放弃锁
参考