"http-bio-8080-exec-1-SendThread(192.168.161.36:2181)" daemon prio=10 tid=0x00007f797425b800 nid=0x3884 runnable [0x00007f79795ac000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:228)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:81)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:87)
- locked <0x00000000f2bf18e0> (a sun.nio.ch.Util$2)
- locked <0x00000000f2bf18f0> (a java.util.Collections$UnmodifiableSet)
- locked <0x00000000f2bf1898> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:98)
at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:349)
at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1081)
http-bio-8080-exec-1-SendThread(192.168.161.36:2181):线程的名字
daemon:守护线程
prio=10:线程的优先级(默认是5)
tid:Java的线程Id(线程在当前虚拟机中的唯一标识)
nid:线程本地标识
runnable:线程的状态
[0x00007f79795ac000]:当前运行的线程在堆中的地址范围
这个线程转储的剩余部分是调用堆栈,这个线程(http-bio-8080-exec-1-SendThread(192.168.161.36:2181))是操作系统守护线程,当前正在执行一个本地方法epollWait。该native线程正处于RUNNABLE的状态,
"http-bio-8080-exec-1" daemon prio=10 tid=0x0000000002172800 nid=0x3882 waiting on condition [0x00007f798c292000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f2a44710> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
该转储表示守护线程http-bio-8080-exec-1会一直等待,直到ThreadPoolExecutor上有需要执行的任务分配给该线程执行(LockSupport.unpark),它才能从LockSupport.park中解脱出来。
"RMI Scheduler(0)" daemon prio=9 tid=7f94661c1800 nid=0x11b479000 waiting on condition [11b478000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <7f40deae0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:196)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2025)
at java.util.concurrent.DelayQueue.take(DelayQueue.java:164)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:609)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:602)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:957)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:917)
at java.lang.Thread.run(Thread.java:695)
Locked ownable synchronizers:
- None
线程RMI Scheduler不会分配CPU执行时间,直到系统自动唤醒,不需要别的线程显示唤醒。它由于调用了LockSupport.parkNanos()进入了有限期等待。
CPU负载高
Java进程CPU占用率居高不下,导致系统吞吐率下降,这种问题对很多初级码农来说简直就是梦魇。如果想快速的定位CPU高的原因,线程转储绝对是第一选择。CPU过高一般原因分为CPU密集型和死循环,这两种情况我们分开来分析。
死循环分析非常简单,步骤如下:
在CPU占用率高的时候,通过top -H或者ps H -eo user,pid,ppid,tid,time,%cpu,cmd --sort=%cpu将CPU占用率高的线程的tid给找出来,并即时线程转储。
将上面的找到的tid进行转化为16进制,然后在线程转储中查找对应的线程号,都会找到对应的nid标示。
定位之后,通过转储中的精确的行号,可以定位到代码中相应的位置,便可以迅速确定原因。
死循环一般是逻辑错误导致的,我们在代码中应该慎用自旋。在一些带有状态机逻辑的代码中,加入次数限制,防止程序跑飞。
CPU密集型分析跟死循环不一样,因为你通过查看线程的CPU使用率,你会发现每个线程都不是很高,但是多个加起来之后就不容乐观。这类问题,步骤如下:
在CPU占用率高的时候,通过top -H或者ps H -eo user,pid,ppid,tid,time,%cpu,cmd --sort=%cpu将CPU占用率高的前10个线程的tid给找出来,并即时线程转储。
将上面的找到的tid进行转化为16进制,然后在线程转储中查找对应的线程号,将能够找到的部分标记出来。
然后在标记出来的线程转储中寻找共性。如果通过所有的线程转储,发现线程正在执行同一个方法(同样的行号),几乎就可以确定这就是罪魁祸首了。那就可以查看代码,来做代码级别的分析了,问题便迎刃而解了。
CPU密集型,一般原因是算法效率低导致的。例如,正则表达式写的差,该用Map或者Set结构的却用了List来遍历。
线程占用CPU的类型可以分为如下两种:
us:用户空间占用CPU百分比
sy:内核空间占用CPU百分比
在linux下可以通过top命令查看详细,示例如下:
Cpu(s): 0.2%us, 0.2%sy, 0.0%ni, 99.3%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st
CPU us高的原因主要是执行线程不需要任何挂起动作,且一直执行,导致CPU没有机会去调度执行其他的线程,我们上面分析的都属于这类情况。
CPU sy高的原因主要是线程的运行状态要经常切换,对于这种情况,常见的一种优化方法是减少线程数。
响应慢(低负载)
相应时间长,一般出现在高负载的机器上。如果在CPU的占用率很低,只有几个线程在消耗CPU的时间片,然而应用的响应时间却很长,我们首先要想到的是IO操作出现问题,至于到底是网络IO还是本地IO,我们就可以通过线程转储来定位。定位到位置之后,可以使用缓存或者减少IO操作来提升系统响应速度。
应用/服务宕机
当一个应用活着却不能完成任何响应的时候,表明该服务已经宕机。
此时分析线程转储,会发现大量的线程在同一个操作中罢工了,没有任何一个线程能够完成自己的操作,从而导致该JVM再没有可用的线程。很多时候都是由于死锁导致的,当一个线程持有一个对象锁而不释放,而别的线程都在等待该锁的时候便会发生死锁现象。幸运的是JVM通常会检测死锁,通过工具分析转储能够更快的定位到原因。如果想避免复杂的死锁,我们需要尽可能减小同步块的大小,并且在进行资源的访问时设置合理的超时时间,避免死等现象。
综上所述,如果能够熟练分析线程转储,就可以在较短时间内识别,诊断,检测和解决生产环境的问题,这对于需要7*24服务的应用来说必备的技能之一。
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "//hm.baidu.com/hm.js?2ea998c61c5fca0f866fd64121472473";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>