JAVA线程dump的分析.docx

上传人:b****2 文档编号:3086847 上传时间:2023-05-05 格式:DOCX 页数:12 大小:36.96KB
下载 相关 举报
JAVA线程dump的分析.docx_第1页
第1页 / 共12页
JAVA线程dump的分析.docx_第2页
第2页 / 共12页
JAVA线程dump的分析.docx_第3页
第3页 / 共12页
JAVA线程dump的分析.docx_第4页
第4页 / 共12页
JAVA线程dump的分析.docx_第5页
第5页 / 共12页
JAVA线程dump的分析.docx_第6页
第6页 / 共12页
JAVA线程dump的分析.docx_第7页
第7页 / 共12页
JAVA线程dump的分析.docx_第8页
第8页 / 共12页
JAVA线程dump的分析.docx_第9页
第9页 / 共12页
JAVA线程dump的分析.docx_第10页
第10页 / 共12页
JAVA线程dump的分析.docx_第11页
第11页 / 共12页
JAVA线程dump的分析.docx_第12页
第12页 / 共12页
亲,该文档总共12页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

JAVA线程dump的分析.docx

《JAVA线程dump的分析.docx》由会员分享,可在线阅读,更多相关《JAVA线程dump的分析.docx(12页珍藏版)》请在冰点文库上搜索。

JAVA线程dump的分析.docx

JAVA线程dump的分析

JAVA线程dump的分析---jstackpid

Java的线程

线程是指能独立于程序的其它部分运行的执行单元。

JAVA语言能够很好的实现多线程的程序。

我们在调试程序,或者在开发后期需要做性能调优的时候,往往也需要了解当前程序正在运行的线程的状态,正在执行的操作,从而分析系统可能存在的问题。

 

在阅读本文之间,应对Java线程的编程原理,同步机制有一定了解. 

产生JAVA线程dump

JAVA的线程DUMP,就象当前JAVA进程的一个快照,打印出所有线程的状态和调用堆栈,以及Monitor的状态。

在不同的操作系统下,产生线程DUMP的方式是不同的。

 

∙∙在windows环境中,

        在启动程序的控制台里敲:

Ctrl-Break,线程的dump会产生在标准输出中(缺省标准输出就是控制台,如果对输出进行了重定向,则要查看输出文件)。

∙在unix,linux和MacOS环境中,

         在控制台中敲:

Ctrl-\,或者, 

         用“kill-3”,或者“kill–QUIT”。

Pid是用所关注的JAVA进程号,您可以用“ps-ef|grepjava”找到,或者使用JDK5.0中的“jps-v”命令获得。

 

∙在各个操作系统平台,都可以用JDK5.0工具包中的jstack

这里要注意的是:

 

1.    不同的JAVA虚机的线程DUMP的创建方法和文件格式是不一样的,不同的JVM版本,dump信息也有差别。

本文中,只以SUN的hotspotJVM5.0_06为例。

 

2.    在实际运行中,往往一次dump的信息,还不足以确认问题。

建议产生三次dump信息,如果每次dump都指向同一个问题,我们才确定问题的典型性。

 

线程分析:

1.    JVM线程 

在线程中,有一些JVM内部的后台线程,来执行譬如垃圾回收,或者低内存的检测等等任务,这些线程往往在JVM初始化的时候就存在,如下所示:

 

       "LowMemoryDetector"daemonprio=10tid=0x081465f8nid=0x7runnable[0x00000000..0x00000000] 

       "CompilerThread0"daemonprio=10tid=0x08143c58nid=0x6waitingoncondition[0x00000000..0xfb5fd798] 

       "SignalDispatcher"daemonprio=10tid=0x08142f08nid=0x5waitingoncondition[0x00000000..0x00000000] 

       "Finalizer"daemonprio=10tid=0x08137ca0nid=0x4inObject.wait()[0xfbeed000..0xfbeeddb8] 

       atjava.lang.Object.wait(NativeMethod) 

       -waitingon<0xef600848>(ajava.lang.ref.ReferenceQueue$Lock) 

       atjava.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:

116) 

       -locked<0xef600848>(ajava.lang.ref.ReferenceQueue$Lock) 

       atjava.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:

132) 

       atjava.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:

159) 

       "ReferenceHandler"daemonprio=10tid=0x081370f0nid=0x3inObject.wait()[0xfbf4a000..0xfbf4aa38] 

       atjava.lang.Object.wait(NativeMethod) 

       -waitingon<0xef600758>(ajava.lang.ref.Reference$Lock) 

       atjava.lang.Object.wait(Object.java:

474) 

       atjava.lang.ref.Reference$ReferenceHandler.run(Reference.java:

116) 

       -locked<0xef600758>(ajava.lang.ref.Reference$Lock) 

       "VMThread"prio=10tid=0x08134878nid=0x2runnable 

       "VMPeriodicTaskThread"prio=10tid=0x08147768nid=0x8waitingoncondition 

               

               我们更多的是要观察用户级别的线程,如下所示:

 

  

       "Thread-1"prio=10tid=0x08223860nid=0xawaitingoncondition[0xef47a000..0xef47ac38] 

       atjava.lang.Thread.sleep(NativeMethod) 

       attestthread.MySleepingThread.method2(MySleepingThread.java:

53) 

       -locked<0xef63d600>(atestthread.MySleepingThread) 

       attestthread.MySleepingThread.run(MySleepingThread.java:

35) 

       atjava.lang.Thread.run(Thread.java:

595) 

1,

  

       我们能看到:

 

∙线程的状态:

waitingoncondition

∙线程的调用栈

∙线程的当前锁住的资源:

<0xef63d600>

       这些信息对我们随后的分析都有用处。

 

2.    线程的状态分析 

正如我们刚看到的那样,线程的状态是一个重要的指标,它会显示在线程Stacktrace的头一行结尾的地方。

那么线程常见的有哪些状态呢?

线程在什么样的情况下会进入这种状态呢?

我们能从中发现什么线索?

 

1.1Runnable 

该状态表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。

 

1.2Waitoncondition 

该状态出现在线程等待某个条件的发生。

具体是什么原因,可以结合stacktrace来分析。

最常见的情况是线程在等待网络的读写,比如当网络数据没有准备好读时,线程处于这种等待状态,而一旦有数据准备好读之后,线程会重新激活,读取并处理数据。

在Java引入NewIO之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。

在NewIO里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。

 

如果发现有大量的线程都在处在Waitoncondition,从线程stack看,正等待网络读写,这可能是一个网络瓶颈的征兆。

因为网络阻塞导致线程无法执行。

一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。

所以要结合系统的一些性能观察工具来综合分析,比如netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制;观察cpu的利用率,如果系统态的CPU时间,相对于用户态的CPU时间比例较高;如果程序运行在Solaris10平台上,可以用dtrace工具看系统调用的情况,如果观察到read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。

 

另外一种出现Waitoncondition的常见情况是该线程在sleep,等待sleep的时间到了时候,将被唤醒。

 

1.3Waitingformonitorentry和inObject.wait() 

在多线程的JAVA程序中,实现线程之间的同步,就要说说Monitor。

Monitor是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者Class的锁。

每一个对象都有,也仅有一个monitor。

下面这个图,描述了线程和Monitor之间关系,以及线程的状态转换图:

 

 

从图中可以看出,每个Monitor在某个时刻,只能被一个线程拥有,该线程就是“ActiveThread”,而其它线程都是“WaitingThread”,分别在两个队列“EntrySet”和“WaitSet”里面等候。

在“EntrySet”中等待的线程状态是“Waitingformonitorentry”,而在“WaitSet”中等待的线程状态是“inObject.wait()”。

 

先看“EntrySet”里面的线程。

我们称被synchronized保护起来的代码段为临界区。

当一个线程申请进入临界区时,它就进入了“EntrySet”队列。

对应的code就像:

 

synchronized(obj){ 

......... 

这时有两种可能性:

 

·         该monitor不被其它线程拥有,EntrySet里面也没有其它等待线程。

本线程即成为相应类或者对象的Monitor的Owner,执行临界区的代码 

·         该monitor被其它线程拥有,本线程在EntrySet队列中等待。

 

在第一种情况下,线程将处于“Runnable”的状态,而第二种情况下,线程DUMP会显示处于“waitingformonitorentry”。

如下所示:

 

  

"Thread-0"prio=10tid=0x08222eb0nid=0x9waitingformonitorentry[0xf927b000..0xf927bdb8] 

attestthread.WaitThread.run(WaitThread.java:

39) 

-waitingtolock<0xef63bf08>(ajava.lang.Object) 

-locked<0xef63beb8>(ajava.util.ArrayList) 

atjava.lang.Thread.run(Thread.java:

595) 

  

临界区的设置,是为了保证其内部的代码执行的原子性和完整性。

但是因为临界区在任何时间只允许线程串行通过,这和我们多线程的程序的初衷是相反的。

如果在多线程的程序中,大量使用synchronized,或者不适当的使用了它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。

如果在线程DUMP中发现了这个情况,应该审查源码,改进程序。

 

现在我们再来看现在线程为什么会进入“WaitSet”。

当线程获得了Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被synchronized的对象)的wait()方法,放弃了Monitor,进入“WaitSet”队列。

只有当别的线程在该对象上调用了notify()或者notifyAll(),“WaitSet”队列中线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。

在“WaitSet”中的线程,DUMP中表现为:

inObject.wait(),类似于:

 

       "Thread-1"prio=10tid=0x08223250nid=0xainObject.wait()[0xef47a000..0xef47aa38] 

       atjava.lang.Object.wait(NativeMethod) 

       -waitingon<0xef63beb8>(ajava.util.ArrayList) 

       atjava.lang.Object.wait(Object.java:

474) 

       attestthread.MyWaitThread.run(MyWaitThread.java:

40) 

       -locked<0xef63beb8>(ajava.util.ArrayList) 

       atjava.lang.Thread.run(Thread.java:

595) 

  

仔细观察上面的DUMP信息,你会发现它有以下两行:

 

-locked<0xef63beb8>(ajava.util.ArrayList) 

-waitingon<0xef63beb8>(ajava.util.ArrayList) 

这里需要解释一下,为什么先lock了这个对象,然后又waitingon同一个对象呢?

让我们看看这个线程对应的代码:

 

  

       synchronized(obj){ 

              ......... 

              obj.wait(); 

              ......... 

       } 

线程的执行中,先用synchronized获得了这个对象的Monitor(对应于locked<0xef63beb8>)。

当执行到obj.wait(),线程即放弃了Monitor的所有权,进入“waitset”队列(对应于waitingon<0xef63beb8>)。

 

往往在你的程序中,会出现多个类似的线程,他们都有相似的DUMP信息。

这也可能是正常的。

比如,在程序中,有多个服务线程,设计成从一个队列里面读取请求数据。

这个队列就是lock以及waitingon的对象。

当队列为空的时候,这些线程都会在这个队列上等待,直到队列有了数据,这些线程被Notify,当然只有一个线程获得了lock,继续执行,而其它线程继续等待。

 

3.    JDK5.0的lock 

上面我们提到如果synchronized和monitor机制运用不当,可能会造成多线程程序的性能问题。

在JDK5.0中,引入了Lock机制,从而使开发者能更灵活的开发高性能的并发多线程程序,可以替代以往JDK中的synchronized和Monitor的机制。

但是,要注意的是,因为Lock类只是一个普通类,JVM无从得知Lock对象的占用情况,所以在线程DUMP中,也不会包含关于Lock的信息,关于死锁等问题,就不如用synchronized的编程方式容易识别。

 

案例分析

1.    死锁 

在多线程程序的编写中,如果不适当的运用同步机制,则有可能造成程序的死锁,经常表现为程序的停顿,或者不再响应用户的请求。

 

比如在下面这个示例中,是个较为典型的死锁情况:

 

  

"Thread-1"prio=5tid=0x00acc490nid=0xe50waitingformonitorentry[0x02d3f000 

..0x02d3fd68] 

atdeadlockthreads.TestThread.run(TestThread.java:

31) 

-waitingtolock<0x22c19f18>(ajava.lang.Object) 

-locked<0x22c19f20>(ajava.lang.Object) 

  

"Thread-0"prio=5tid=0x00accdb0nid=0xdecwaitingformonitorentry[0x02cff000 

..0x02cff9e8] 

atdeadlockthreads.TestThread.run(TestThread.java:

31) 

-waitingtolock<0x22c19f20>(ajava.lang.Object) 

-locked<0x22c19f18>(ajava.lang.Object) 

在JAVA5中加强了对死锁的检测。

线程Dump中可以直接报告出Java级别的死锁,如下所示:

 

  

FoundoneJava-leveldeadlock:

 

============================= 

"Thread-1":

 

waitingtolockmonitor0x0003f334(object0x22c19f18,ajava.lang.Object), 

whichisheldby"Thread-0" 

"Thread-0":

 

waitingtolockmonitor0x0003f314(object0x22c19f20,ajava.lang.Object), 

whichisheldby"Thread-1" 

2.    热锁 

热锁,也往往是导致系统性能瓶颈的主要因素。

其表现特征为,由于多个线程对临界区,或者锁的竞争,可能出现:

&lt;/span> 

∙频繁的线程的上下文切换:

从操作系统对线程的调度来看,当线程在等待资源而阻塞的时候,操作系统会将之切换出来,放到等待的队列,当线程获得资源之后,调度算法会将这个线程切换进去,放到执行队列中。

&amp;amp;lt;/li>

∙大量的系统调用:

因为线程的上下文切换,以及热锁的竞争,或者临界区的频繁的进出,都可能导致大量的系统调用。

Mt

∙大部分CPU开销用在“系统态”:

线程上下文切换,和系统调用,都会导致CPU在“系统态”运行,换而言之,虽然系统很忙碌,但是CPU用在“用户态”的比例较小,应用程序得不到充分的CPU资源。

∙随着CPU数目的增多,系统的性能反而下降。

因为CPU数目多,同时运行的线程就越多,可能就会造成更频繁的线程上下文切换和系统态的CPU开销,从而导致更糟糕的性能。

上面的描述,都是一个scalability(可扩展性)很差的系统的表现。

从整体的性能指标看,由于线程热锁的存在,程序的响应时间会变长,吞吐量会降低。

 

那么,怎么去了解“热锁”出现在什么地方呢?

一个重要的方法还是结合操作系统的各种工具观察系统资源使用状况,以及收集Java线程的DUMP信息,看线程都阻塞在什么方法上,了解原因,才能找到对应的解决方法。

 

我们曾经遇到过这样的例子,程序运行时,出现了以上指出的各种现象,通过观察操作系统的资源使用统计信息,以及线程DUMP信息,确定了程序中热锁的存在,并发现大多数的线程状态都是Waitingformonitorentry或者Waitonmonitor,且是阻塞在压缩和解压缩的方法上。

后来采用第三方的压缩包javalib替代JDK自带的压缩包后,系统的性能提高了几倍。

 

总结

本文就介绍了Java线程DUMP的基本知识和分析的基本方法,并且解释了如何利用线程的DUMP信息,以及结合操作系统的各种资源使用情况,分析程序的性能问题,从而达到改进程序,提高性能的目的。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2