Thread Dump先容
什么是Thread Dump
Thread Dump黑白常有效的诊断Java应用题目的工具。每一个Java假造机都有实时生玉成部线程在某一点状态的thread-dump的本领,固然各个 Java假造机打印的thread dump略有差别,但是 大多都提供了当前运动线程的快照,及JVM中全部Java线程的堆栈跟踪信息,堆栈信息一样平常包罗完备的类名及所实行的方法,假如大概的话尚有源代码的行数。
Thread Dump特点
- 能在各种操纵体系下使用;
- 能在各种Java应用服务器下使用;
- 能在生产环境下使用而不影响体系的性能;
- 能将题目直接定位到应用步伐的代码行上;
Thread Dump抓取
一样平常当服务器挂起,瓦解大概性能低下时,就须要抓取服务器的线程堆栈(Thread Dump)用于后续的分析。在现实运行中,每每一次 dump的信息,还不敷以确认题目。为了反映线程状态的动态变革,须要接连多次做thread dump,每次隔断10-20s,发起至少产生三次 dump信息,假如每次 dump都指向同一个题目,我们才确定题目的范例性。
- ps –ef | grep java
- kill -3 <pid>
复制代码 留意:
肯定要审慎, 一步不慎就大概让服务器历程被杀死。kill -9 下令会杀死历程。
- jps 或 ps –ef | grep java (获取PID)
- jstack [-l ] <pid> | tee -a jstack.log(获取ThreadDump)
复制代码 Thread Dump分析
Thread Dump信息
- 2011-11-02 19:05:06
- Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode):
复制代码- 1. "Timer-0" daemon prio=10 tid=0xac190c00 nid=0xaef in Object.wait() [0xae77d000]
- # 线程名称:Timer-0;线程类型:daemon;优先级: 10,默认是5;
- # JVM线程id:tid=0xac190c00,JVM内部线程的唯一标识(通过java.lang.Thread.getId()获取,通常用自增方式实现)。
- # 对应系统线程id(NativeThread ID):nid=0xaef,和top命令查看的线程pid对应,不过一个是10进制,一个是16进制。(通过命令:top -H -p pid,可以查看该进程的所有线程信息)
- # 线程状态:in Object.wait();
- # 起始栈地址:[0xae77d000],对象的内存地址,通过JVM内存查看工具,能够看出线程是在哪儿个对象上等待;
- 2. java.lang.Thread.State: TIMED_WAITING (on object monitor)
- 3. at java.lang.Object.wait(Native Method)
- 4. -waiting on <0xb3885f60> (a java.util.TaskQueue) # 继续wait
- 5. at java.util.TimerThread.mainLoop(Timer.java:509)
- 6. -locked <0xb3885f60> (a java.util.TaskQueue) # 已经locked
- 7. at java.util.TimerThread.run(Timer.java:462)
复制代码 Java thread statck trace:是上面2-7行的信息。到现在为止这是最紧张的数据,Java stack trace提供了大部分信息来准确定位题目根源。
- Java thread statck trace详解:
堆栈信息应该逆向解读:步伐先实行的是第7行,然后是第6行,依次类推。- - locked <0xb3885f60> (a java.util.ArrayList)
- - waiting on <0xb3885f60> (a java.util.ArrayList)
复制代码 也就是说对象先上锁,锁住对象0xb3885f60,然后开释该对象锁,进入waiting状态。为啥会出现如许的环境呢?看看下面的java代码示例,就会明白:- synchronized(obj) {
- .........
- obj.wait();
- .........
- }
复制代码 如上,线程的实行过程,先用 synchronized 得到了这个对象的 Monitor(对应于 locked )。当实行到 obj.wait(),线程即放弃了 Monitor的全部权,进入 “wait set”队列(对应于 waiting on )。
在堆栈的第一行信息中,进一步标明白线程在代码级的状态,比方:- java.lang.Thread.State: TIMED_WAITING (parking)
复制代码 表明如下:- |blocked|
- > This thread tried to enter asynchronized block, but the lock was taken by another thread. This thread isblocked until the lock gets released.
- |blocked (on thin lock)|
- > This is the same state asblocked, but the lock in question is a thin lock.
- |waiting|
- > This thread calledObject.wait() on an object. The thread will remain there until some otherthread sends a notification to that object.
- |sleeping|
- > This thread calledjava.lang.Thread.sleep().
- |parked|
- > This thread calledjava.util.concurrent.locks.LockSupport.park().
- |suspended|
- > The thread's execution wassuspended by java.lang.Thread.suspend() or a JVMTI agent call.
复制代码 Thread状态分析
线程的状态是一个很紧张的东西,因此thread dump中会体现这些状态,通过对这些状态的分析,可以大概得出线程的运行状态,进而发现大概存在的题目。线程的状态在Thread.State这个罗列范例中界说:- public enum State
- {
- /**
- * Thread state for a thread which has not yet started.
- */
- NEW,
-
- /**
- * Thread state for a runnable thread. A thread in the runnable
- * state is executing in the Java virtual machine but it may
- * be waiting for other resources from the operating system
- * such as processor.
- */
- RUNNABLE,
-
- /**
- * Thread state for a thread blocked waiting for a monitor lock.
- * A thread in the blocked state is waiting for a monitor lock
- * to enter a synchronized block/method or
- * reenter a synchronized block/method after calling
- * {@link Object#wait() Object.wait}.
- */
- BLOCKED,
-
- /**
- * Thread state for a waiting thread.
- * A thread is in the waiting state due to calling one of the
- * following methods:
- * <ul>
- * <li>{@link Object#wait() Object.wait} with no timeout</li>
- * <li>{@link #join() Thread.join} with no timeout</li>
- * <li>{@link LockSupport#park() LockSupport.park}</li>
- * </ul>
- *
- * <p>A thread in the waiting state is waiting for another thread to
- * perform a particular action.
- *
- * For example, a thread that has called <tt>Object.wait()</tt>
- * on an object is waiting for another thread to call
- * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
- * that object. A thread that has called <tt>Thread.join()</tt>
- * is waiting for a specified thread to terminate.
- */
- WAITING,
-
- /**
- * Thread state for a waiting thread with a specified waiting time.
- * A thread is in the timed waiting state due to calling one of
- * the following methods with a specified positive waiting time:
- * <ul>
- * <li>{@link #sleep Thread.sleep}</li>
- * <li>{@link Object#wait(long) Object.wait} with timeout</li>
- * <li>{@link #join(long) Thread.join} with timeout</li>
- * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
- * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
- * </ul>
- */
- TIMED_WAITING,
-
- /**
- * Thread state for a terminated thread.
- * The thread has completed execution.
- */
- TERMINATED;
- }
复制代码
- NEW:每一个线程,在堆内存中都有一个对应的Thread对象。Thread t = new Thread();当刚刚在堆内存中创建Thread对象,还没有调用t.start()方法之前,线程就处在NEW状态。在这个状态上,线程与平凡的java对象没有什么区别,就仅仅是一个堆内存中的对象。
- RUNNABLE:该状态体现线程具备全部运行条件,在运行队列中准备操纵体系的调理,大概正在运行。 这个状态的线程比力正常,但假如线程长时间停顿在在这个状态就不正常了,这阐明线程运行的时间很长(存在性能题目),大概是线程不绝得不得实行的机遇(存在线程饥饿的题目)。
- BLOCKED:线程正在等候获取java对象的监督器(也叫内置锁),即线程正在等候进入由synchronized掩护的方法大概代码块。synchronized用来包管原子性,恣意时间最多只能由一个线程进入该临界地域,其他线程只能列队等候。
- WAITING:处在该线程的状态,正在等候某个变乱的发生,只有特定的条件满意,才华得到实行机遇。而产生这个特定的变乱,通常都是另一个线程。也就是说,假如不发生特定的变乱,那么处在该状态的线程不绝等候,不能获取实行的机遇。好比:A线程调用了obj对象的obj.wait()方法,假如没有线程调用obj.notify或obj.notifyAll,那么A线程就没有办法规复运行; 假如A线程调用了LockSupport.park(),没有别的线程调用LockSupport.unpark(A),那么A没有办法规复运行
- TIMED_WAITING:J.U.C中许多与线程相干类,都提供了限时版本和不限时版本的API。TIMED_WAITING意味着线程调用了限时版本的API,正在等候时间流逝。当等候时间已今后,线程一样可以规复运行。假如线程进入了WAITING状态,肯定要特定的变乱发生才华规复运行;而处在TIMED_WAITING的线程,假如特定的变乱发生大概是时间流逝完毕,都会规复运行。
- TERMINATED:线程实行完毕,实行完run方法正常返回,大概抛出了运行时非常而竣事,线程都会停顿在这个状态。这个时间线程只剩下Thread对象了,没有什么用了。
关键状态分析
- Wait on condition:The thread is either sleeping or waiting to be notified by another thread.
该状态阐明它在等候另一个条件的发生,来把自己叫醒,大概干脆它是调用了 sleep(n)。
此时线程状态大抵为以下几种:- java.lang.Thread.State: WAITING (parking):一直等那个条件发生;
- java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己。
复制代码
- Waiting for Monitor Entry 和 in Object.wait():The thread is waiting to get the lock for an object (some other thread may be holding the lock). This happens if two or more threads try to execute synchronized code. Note that the lock is always for an object and not for individual methods.
在多线程的JAVA步伐中,实现线程之间的同步,就要说说 Monitor。Monitor是Java中用以实现线程之间的互斥与协作的紧张本领,它可以看成是对象大概Class的锁。每一个对象都有,也仅有一个 Monitor 。下面这个图,形貌了线程和 Monitor之间关系,以及线程的状态转换图:
如上图,每个Monitor在某个时间,只能被一个线程拥有,该线程就是 “ActiveThread”,而别的线程都是 “Waiting Thread”,分别在两个队列“Entry Set”和“Wait Set”里等待。在“Entry Set”中等候的线程状态是“Waiting for monitor entry”,而在“Wait Set”中等候的线程状态是“in Object.wait()”。
先看“Entry Set”内里的线程。我们称被 synchronized掩护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了“Entry Set”队列。对应的 code就像:- synchronized(obj) {
- .........
- }
复制代码 这时有两种大概性:
- 该 monitor不被别的线程拥有, Entry Set内里也没有别的等候线程。本线程即成为相应类大概对象的 Monitor的 Owner,实行临界区的代码。
- 该 monitor被别的线程拥有,本线程在 Entry Set队列中等候。
在第一种环境下,线程将处于 “Runnable”的状态,而第二种环境下,线程 DUMP会体现处于 “waiting for monitor entry”。如下:- "Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8]
- at testthread.WaitThread.run(WaitThread.java:39)
- - waiting to lock <0xef63bf08> (a java.lang.Object)
- - locked <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Thread.run(Thread.java:595)
复制代码 临界区的设置,是为了包管其内部的代码实行的原子性和完备性。但是由于临界区在任何时间只答应线程串行通过,这和我们多线程的步伐的初志是相反的。假如在多线程的步伐中,大量使用 synchronized,大概不得当的使用了它,会造成大量线程在临界区的入口等候,造成体系的性能大幅降落。假如在线程 DUMP中发现了这个环境,应该检察源码,改历步伐。
再看“Wait Set”内里的线程。当线程得到了 Monitor,进入了临界区之后,假如发现线程继续运行的条件没有满意,它则调用对象(一样平常就是被 synchronized 的对象)的 wait() 方法,放弃 Monitor,进入 “Wait Set”队列。只有当别的线程在该对象上调用了 notify() 大概 notifyAll(),“Wait Set”队列中线程才得到机遇去竞争,但是只有一个线程得到对象的Monitor,规复到运行态。在 “Wait Set”中的线程, DUMP中体现为: in Object.wait()。如下:- "Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38]
- at java.lang.Object.wait(Native Method)
- - waiting on <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Object.wait(Object.java:474)
- at testthread.MyWaitThread.run(MyWaitThread.java:40)
- - locked <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Thread.run(Thread.java:595)
复制代码 综上,一样平常CPU很忙时,则关注runnable的线程,CPU很闲时,则关注waiting for monitor entry的线程。
上面提到假如 synchronized和 monitor机制运用不妥,大概会造成多线程步伐的性能题目。在 JDK 5.0中,引入了 Lock机制,从而使开辟者能更机动的开辟高性能的并发多线程步伐,可以更换以往 JDK中的 synchronized和 Monitor的 机制。但是,要留意的是,由于 Lock类只是一个平凡类,JVM无从得知 Lock对象的占用环境,以是在线程 DUMP中,也不会包罗关于 Lock的信息, 关于死锁等题目,就不如用 synchronized的编程方式容易辨认。
案例分析
题目场景
- CPU飙高,load高,相应很慢
- 一个哀求过程中连续取3-5个ThreadDump(隔断5-10秒)
- 对比多次dump文件的runnable线程
- 查找占用CPU最多的线程
- 使用下令:top -H -p pid(pid为被测体系的历程号),找到导致CPU高的线程ID,对应thread dump信息中线程的nid,只不外一个是十进制,一个是十六进制;
- 在thread dump中,根据top下令查找的线程id,查找对应的线程堆栈信息;
- 大概存在的题目:
- 假如实行的方法有比力大变革,阐明比力正常。
- 假如在实行同一个方法,就有一些题目了:比方某个盘算方法在循环中大量盘算,或数据库查询没有使用索引。
- 大概哀求无法相应:
- 查抄是否有死锁(ThreadDump开头会明白提示)
- 检察大量线程是否处于BLOCKED状态(锁竞争)
- 分析线程是否在WAITING某个条件但永久等不到
死锁
死锁常常体现为步伐的停顿,大概不再相应用户的哀求。从操纵体系上观察,对应历程的CPU占用率为零,很快会从top或prstat的输出中消散。
好比在下面这个示例中,是个较为范例的死锁环境:- "Thread-1" prio=5 tid=0x00acc490 nid=0xe50 waiting for monitor entry [0x02d3f000
- ..0x02d3fd68]
- at deadlockthreads.TestThread.run(TestThread.java:31)
- - waiting to lock <0x22c19f18> (a java.lang.Object)
- - locked <0x22c19f20> (a java.lang.Object)
- "Thread-0" prio=5 tid=0x00accdb0 nid=0xdec waiting for monitor entry [0x02cff000
- ..0x02cff9e8]
- at deadlockthreads.TestThread.run(TestThread.java:31)
- - waiting to lock <0x22c19f20> (a java.lang.Object)
- - locked <0x22c19f18> (a java.lang.Object)
复制代码 在 JAVA 5中增强了对死锁的检测。线程 Dump中可以直接陈诉出 Java级别的死锁,如下所示:- Found one Java-level deadlock:
- =============================
- "Thread-1":
- waiting to lock monitor 0x0003f334 (object 0x22c19f18, a java.lang.Object),
- which is held by "Thread-0"
- "Thread-0":
- waiting to lock monitor 0x0003f314 (object 0x22c19f20, a java.lang.Object),
- which is held by "Thread-1"
复制代码 热锁
热锁,也每每是导致体系性能瓶颈的紧张因素。其体现特性为:由于多个线程对临界区,大概锁的竞争,大概出现:
- 频仍的线程的上下文切换:从操纵体系对线程的调理来看,当线程在等候资源而壅闭的时间,操纵体系会将之切换出来,放到等候的队列,当线程得到资源之后,调理算法会将这个线程切换进去,放到实行队列中。
- 大量的体系调用:由于线程的上下文切换,以及热锁的竞争,大概临界区的频仍的收支,都大概导致大量的体系调用。
- 大部分CPU开销用在“体系态”:线程上下文切换,和体系调用,都会导致 CPU在 “体系态 ”运行,换而言之,固然体系很繁忙,但是CPU用在 “用户态 ”的比例较小,应用步伐得不到充实的 CPU资源。
- 随着CPU数量的增多,体系的性能反而降落。由于CPU数量多,同时运行的线程就越多,大概就会造成更频仍的线程上下文切换和体系态的CPU开销,从而导致更糟糕的性能。
上面的形貌,都是一个 scalability(可扩展性)很差的体系的体现。从团体的性能指标看,由于线程热锁的存在,步伐的相应时间会变长,吞吐量会低沉。
那么,怎么去相识 “热锁 ”出现在什么地方呢?
一个紧张的方法是 连合操纵体系的各种工具观察体系资源使用状态,以及网络Java线程的DUMP信息,看线程都壅闭在什么方法上,相识缘故原由,才华找到对应的办理方法。
内存走漏关联分析
症状:频仍Full GC,内存不开释
ThreadDump分析步调:
1. 连合HeapDump分析
2. 在ThreadDump中查找持有大量对象的线程
3. 查抄线程的调用栈,找到创建这些对象的业务逻辑
范例发现:静态聚集不停添加对象且不实时整理。
JVM紧张线程
JVM运行过程中产生的一些比力紧张的线程摆列如下:
线程名称表明阐明Attach ListenerAttach Listener 线程是负责吸收到外部的下令,而对该下令举行实行的并把结果返回给发送者。通常我们会用一些下令去要求JVM给我们一些反馈信息,如:java -version、jmap、jstack等等。 假如该线程在JVM启动的时间没有初始化,那么,则会在用户第一次实行JVM下令时,得到启动。Signal Dispatcher前面提到Attach Listener线程的职责是吸收外部JVM下令,当下令吸收乐成后,会交给signal dispather线程去举行分发到各个差别的模块处置惩罚下令,而且返回处置惩罚结果。signal dispather线程也是在第一次吸收外部JVM下令时,举行初始化工作。CompilerThread0用来调用JITing,实时编译装卸class 。 通常,JVM会启动多个线程来处置惩罚这部分工作,线程名称反面的数字也会累加,比方:CompilerThread1。Concurrent Mark-Sweep GC Thread并发标志打扫垃圾接纳器(就是通常所说的CMS GC)线程, 该线程紧张针对于老年代垃圾接纳。ps:启用该垃圾接纳器,须要在JVM启动参数中加上:-XX:+UseConcMarkSweepGC。DestroyJavaVM实行main()的线程,在main实行完后调用JNI中的 jni_DestroyJavaVM() 方法唤起DestroyJavaVM 线程,处于等候状态,等候别的线程(Java线程和Native线程)退出时关照它卸载JVM。每个线程退出时,都会判定自己当前是否是整个JVM中末了一个非deamon线程,假如是,则关照DestroyJavaVM 线程卸载JVM。Finalizer Thread这个线程也是在main线程之后创建的,其优先级为10,紧张用于在垃圾网络前,调用对象的finalize()方法;关于Finalizer线程的几点:1) 只有当开始一轮垃圾网络时,才会开始调用finalize()方法;因此并不是全部对象的finalize()方法都会被实行;2) 该线程也是daemon线程,因此假如假造机中没有其他非daemon线程,不管该线程有没有实行完finalize()方法,JVM也会退出;3) JVM在垃圾网络时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处置惩罚;末了将该Finalizer对象的引用置为null,由垃圾网络器来接纳;4) JVM为什么要单独用一个线程来实行finalize()方法呢?假如JVM的垃圾网络线程自己来做,很有大概由于在finalize()方法中误操纵导致GC线程制止或不可控,这对GC线程来说是一种灾难;Low Memory Detector这个线程是负责对可使用内存举行检测,假如发现可用内存低,分配新的内存空间。Reference HandlerJVM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它紧张用于处置惩罚引用对象自己(软引用、弱引用、虚引用)的垃圾接纳题目 。VM Thread这个线程就比力牛b了,是JVM内里的线程母体,根据hotspot源码(vmThread.hpp)内里的表明,它是一个单个的对象(最原始的线程)会产生或触发全部其他的线程,这个单个的VM线程是会被其他线程所使用来做一些VM操纵(如:排除垃圾等)。 |