文档章节

jstack和线程dump分析

xiaoxin
 xiaoxin
发布于 2013/12/30 15:16
字数 3077
阅读 160
收藏 11
点赞 0
评论 1

一:jstack

jstack命令的语法格式: jstack  <pid>。可以用jps查看java进程id。这里要注意的是:
1. 不同的 JAVA虚机的线程 DUMP的创建方法和文件格式是不一样的,不同的 JVM版本, dump信息也有差别。本文中,只以 SUN的 hotspot JVM 5.0_06 为例。
2. 在实际运行中,往往一次 dump的信息,还不足以确认问题。建议产生三次 dump信息,如果每次 dump都指向同一个问题,我们才确定问题的典型性。



二:线程分析
2.1. JVM 线程

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

Html代码  收藏代码

  1. <span style="font-size: small;">  "Low Memory Detector" daemon prio=10 tid=0x081465f8 nid=0x7 runnable [0x00000000..0x00000000]  

  2.         "CompilerThread0" daemon prio=10 tid=0x08143c58 nid=0x6 waiting on condition [0x00000000..0xfb5fd798]  

  3.         "Signal Dispatcher" daemon prio=10 tid=0x08142f08 nid=0x5 waiting on condition [0x00000000..0x00000000]  

  4.         "Finalizer" daemon prio=10 tid=0x08137ca0 nid=0x4 in Object.wait() [0xfbeed000..0xfbeeddb8]  

  5.   

  6.         at java.lang.Object.wait(Native Method)  

  7.   

  8.         - waiting on <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)  

  9.   

  10.         at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)  

  11.   

  12.         - locked <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)  

  13.   

  14.         at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)  

  15.   

  16.         at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)  

  17.   

  18.         "Reference Handler" daemon prio=10 tid=0x081370f0 nid=0x3 in Object.wait() [0xfbf4a000..0xfbf4aa38]  

  19.   

  20.         at java.lang.Object.wait(Native Method)  

  21.   

  22.         - waiting on <0xef600758> (a java.lang.ref.Reference$Lock)  

  23.   

  24.         at java.lang.Object.wait(Object.java:474)  

  25.   

  26.         at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)  

  27.   

  28.         - locked <0xef600758> (a java.lang.ref.Reference$Lock)  

  29.   

  30.         "VM Thread" prio=10 tid=0x08134878 nid=0x2 runnable  

  31.   

  32.         "VM Periodic Task Thread" prio=10 tid=0x08147768 nid=0x8 waiting on condition</span>  

 

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

Html代码  收藏代码

  1. <span style="font-size: small;">   "Thread-1" prio=10 tid=0x08223860 nid=0xa waiting on condition [0xef47a000..0xef47ac38]  

  2.   

  3.         at java.lang.Thread.sleep(Native Method)  

  4.   

  5.         at testthread.MySleepingThread.method2(MySleepingThread.java:53)  

  6.   

  7.         - locked <0xef63d600> (a testthread.MySleepingThread)  

  8.   

  9.         at testthread.MySleepingThread.run(MySleepingThread.java:35)  

  10.   

  11.         at java.lang.Thread.run(Thread.java:595) </span>  

我们能看到:
    * 线程的状态: waiting on condition
    * 线程的调用栈
    * 线程的当前锁住的资源: <0xef63d600>



2.2. 线程的状态分析

       正如我们刚看到的那样,线程的状态是一个重要的指标,它会显示在线程 Stacktrace的头一行结尾的地方。那么线程常见的有哪些状态呢?线程在什么样的情况下会进入这种状态呢?我们能从中发现什么线索?< /span>
1.1 Runnable
该状态表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。

1.2 Wait on condition
       该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stacktrace来分析。最常见的情况是线程在等待网络的读写,比如当网络数据没有准备好读时,线程处于这种等待状态,而一旦有数据准备好读之后,线 程会重新激活,读取并处理数据。在 Java引入 NewIO之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪 费,而且给操作系统的线程调度也带来压力。在 NewIO里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。
        如果发现有大量的线程都在处在 Wait on condition,从线程 stack看, 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几 乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的 CPU时间,相对于用户态的 CPU时间比例较高;如果程序运行在 Solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。另外一种出现 Wait on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。

1.3 Waiting for monitor entry 和 in Object.wait()
         在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “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”。如下所示:

Html代码  收藏代码

  1. "Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8]  

  2.   

  3. at testthread.WaitThread.run(WaitThread.java:39)  

  4.   

  5. - waiting to lock <0xef63bf08> (a java.lang.Object)  

  6.   

  7. - locked <0xef63beb8> (a java.util.ArrayList)  

  8.   

  9. 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(),类似于:

Html代码  收藏代码

  1. "Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38]  

  2.   

  3.         at java.lang.Object.wait(Native Method)  

  4.   

  5.         - waiting on <0xef63beb8> (a java.util.ArrayList)  

  6.   

  7.         at java.lang.Object.wait(Object.java:474)  

  8.   

  9.         at testthread.MyWaitThread.run(MyWaitThread.java:40)  

  10.   

  11.         - locked <0xef63beb8> (a java.util.ArrayList)  

  12.   

  13.         at java.lang.Thread.run(Thread.java:595)  

仔细观察上面的 DUMP信息,你会发现它有以下两行:
- locked <0xef63beb8> (a java.util.ArrayList)
- waiting on <0xef63beb8> (a java.util.ArrayList)
这里需要解释一下,为什么先 lock了这个对象,然后又 waiting on同一个对象呢?让我们看看这个线程对应的代码:

Java代码  收藏代码

  1. synchronized(obj) {  

  2.        .........  

  3.        obj.wait();  

  4.        .........  

  5. }   

线程的执行中,先用 synchronized 获得了这个对象的 Monitor(对应于 locked <0xef63beb8> )。当执行到 obj.wait(), 线程即放弃了 Monitor的所有权,进入 “wait set”队列(对应于 waiting on <0xef63beb8> )。
         往往在你的程序中,会出现多个类似的线程,他们都有相似的 DUMP信息。这也可能是正常的。比如,在程序中,有多个服务线程,设计成从一个队列里面读取请求数据。这个队列就是 lock以及 waiting on的对象。当队列为空的时候,这些线程都会在这个队列上等待,直到队列有了数据,这些线程被 Notify,当然只有一个线程获得了 lock,继续执行,而其它线程继续等待。

3. JDK 5.0 的 lock
        上面我们提到如果 synchronized和 monitor机制运用不当,可能会造成多线程程序的性能问题。在 JDK 5.0中,引入了 Lock机制,从而使开发者能更灵活的开发高性能的并发多线程程序,可以替代以往 JDK中的 synchronized和 Monitor的 机制。但是,要注意的是,因为 Lock类只是一个普通类, JVM无从得知 Lock对象的占用情况,所以在线程 DUMP中,也不会包含关于 Lock的信息, 关于死锁等问题,就不如用 synchronized的编程方式容易识别。

4.案例分析
1.     死锁
在多线程程序的编写中,如果不适当的运用同步机制,则有可能造成程序的死锁,经常表现为程序的停顿,或者不再响应用户的请求。比如在下面这个示例中,是个较为典型的死锁情况:

Java代码  收藏代码

  1. "Thread-1" prio=5 tid=0x00acc490 nid=0xe50 waiting for monitor entry [0x02d3f000  

  2.   

  3. ..0x02d3fd68]  

  4.   

  5. at deadlockthreads.TestThread.run(TestThread.java:31)  

  6.   

  7. - waiting to lock <0x22c19f18> (a java.lang.Object)  

  8.   

  9. - locked <0x22c19f20> (a java.lang.Object)  

  10.   

  11. "Thread-0" prio=5 tid=0x00accdb0 nid=0xdec waiting for monitor entry [0x02cff000  

  12.   

  13. ..0x02cff9e8]  

  14.   

  15. at deadlockthreads.TestThread.run(TestThread.java:31)  

  16.   

  17. - waiting to lock <0x22c19f20> (a java.lang.Object)  

  18.   

  19. - locked <0x22c19f18> (a java.lang.Object)  

  20.   

  21.    

 

在 JAVA 5中加强了对死锁的检测。线程 Dump中可以直接报告出 Java级别的死锁,如下所示:

Java代码  收藏代码

  1. Found one Java-level deadlock:  

  2. =============================  

  3. "Thread-1":  

  4. waiting to lock monitor 0x0003f334 (object 0x22c19f18, a java.lang.Object),  

  5. which is held by "Thread-0"  

  6. "Thread-0":  

  7. waiting to lock monitor 0x0003f314 (object 0x22c19f20, a java.lang.Object),  

  8. which is held by "Thread-1"   

 

2.     热锁
        热锁,也往往是导致系统性能瓶颈的主要因素。其表现特征为,由于多个线程对临界区,或者锁的竞争,可能出现:
    * 频繁的线程的上下文切换:从操作系统对线程的调度来看,当 线程在等待资源而阻塞的时候,操作系统会将之切换出来,放到等待的队列,当线程获得资源之后,调度算法会将这个线程切换进去,放到执行队列中。
    * 大量的系统调用:因为线程的上下文切换,以及热锁的竞争,或 者临界区的频繁的进出,都可能导致大量的系统调用。
    * 大部分 CPU开销用在 “系统态 ”:线程上下文切换,和系统调用,都会导致 CPU在 “系统态 ”运行,换而言之,虽然系统很忙碌,但是 CPU用在 “用户态 ”的比例较小,应用程序得不到充分的 CPU资源。

    * 随着 CPU数目的增多,系统的性能反而下降。因为 CPU数目多,同 时运行的线程就越多,可能就会造成更频繁的线程上下文切换和系统态的 CPU开销,从而导致更糟糕的性能。
上面的描述,都是一个 scalability(可扩展性)很差的系统的表现。从整体的性能指标看,由于线程热锁的存在,程序的响应时间会变长,吞吐量会降低。< /span>
         那么,怎么去了解 “热锁 ”出现在什么地方呢?一个重要的方法还是结合操作系统的各种工具观察系统资源使用状况,以及收集 Java线程的 DUMP信息,看线程都阻塞在什么方法上,了解原因,才能找到对应的解决方法。
        我们曾经遇到过这样的例子,程序运行时,出现了以上指出的各种现象,通过观察操作系统的资源使用统计信息,以及线程 DUMP信息,确定了程序中热锁的存在,并发现大多数的线程状态都是 Waiting for monitor entry或者 Wait on monitor,且是阻塞在压缩和解压缩的方法上。后来采用第三方的压缩包 javalib替代 JDK自带的压缩包后,系统的性能提高了几倍。


本文转载自:http://jameswxx.iteye.com/blog/1041173

共有 人打赏支持
xiaoxin
粉丝 17
博文 231
码字总数 18402
作品 0
海淀
项目经理
加载中

评论(1)

lnyu
lnyu
ctrl+c, ctrl+v ?
jstack分析线程状态

背景 记得前段时间,同事说他们测试环境的服务器cpu使用率一直处于100%,本地又没有什么接口调用,为什么会这样?cpu使用率居高不下,自然是有某些线程一直占用着cpu资源,那又如何查看占用c...

爱捣蛋的灰太狼 ⋅ 2017/03/01 ⋅ 0

性能文件分析以及常用的分析工具

1、JPS查看Java进程 2、Jstack转出线程堆栈信息 3、jmap转出堆内存快照 4、jhat分析转出的堆内存快照 5、使用visualVM或jconsole远程监控应用分析。...

器石_ ⋅ 2015/09/09 ⋅ 0

通过JDK自带的命令解决程序运行缓慢,tomcat内存泄露问题(待续)

讲解几个JDK自带命令的用法(顺序从使用频率由高到低): 1. jstat 此命令是用来查看tomcat的运行情况的 jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]] 参数 说明 Options ......

江江的喵 ⋅ 2016/03/10 ⋅ 1

【2016-05-19】一次tomcat频繁挂掉的问题定位

问题: 最近手中一个web项目频繁挂掉,tomcat进程跑着跑着就没了,catalina.out里也没有任何相关报错。 项目功能: 该服务有3台物理机,接收client端的rpc调用,本机起hive进程做查询,将hiv...

rathan0 ⋅ 2016/05/19 ⋅ 1

JVM调优方法总结

在这里我使用的都是JDK自带的命令 JAVA_HOME/bin/* 查看Server PID - jps 如果需要分析某一个Server,首先当然需要知道它的PID.使用jps可以用显示当前jvm的进程,当然也可以使用ps -ef |grep ...

ZooKeeper ⋅ 2014/01/15 ⋅ 0

内存泄露问题排查

发现cpu持续占用高,定位占用cpu比较多的进程 2. 进一步定位进程里面具体哪些线程占用高top -Hp 873 3. 上面线程pid转成16进制,jstack 873找到对应的线程,发现是垃圾回收线程 4. 看看为什么...

Small-Liu ⋅ 2016/06/27 ⋅ 0

虚拟机监控命令工具

命令行工具大多数是基于tools.jar的一层包装,如果是运行在1.5的虚拟机之上,需要开启jmx管理功能,如果是1.6以上,则默认是开启的 jps,显示系统内所有的hotspot虚拟机进程 本地,本地虚拟机...

281165273 ⋅ 2014/04/03 ⋅ 0

java虚拟机故障处理工具

概述 给系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。 java开发人员可以在jdk安装的bin目录下找到除了,以外的其他命令。这些命令主要是一些用于监...

林湾村龙猫 ⋅ 2016/11/26 ⋅ 0

Java应用线上问题排查的常用工具和方法

1、jstackjstack可以告诉你当前所有JVM线程正在做什么,包括用户线程和虚拟机线程,你可以用它来查看线程栈,并且结合Lock信息来检测是否发生了死锁和死锁的线程。 没事儿jstack一下,知道你...

有事没事 ⋅ 2016/10/28 ⋅ 0

Java 开发必须掌握的线上问题排查命令

作为一个合格的开发人员,不仅要能写得一手还代码,还有一项很重要的技能就是排查问题。这里提到的排查问题不仅仅是在coding的过程中debug等,还包括的就是线上问题的排查。由于在生产环境中...

余平的余_余平的平 ⋅ 2017/09/22 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

idea 整合 vue 启动

刚学习Vue 搭建了一个项目 只能命令启动 Idea里面不会启动 尝试了一下修改启动的配置 如下: 1.首先你要保证你的package.json没有修改过 具体原因没有看 因为我改了这个name的值 就没办法启动...

事儿爹 ⋅ 22分钟前 ⋅ 0

数据仓库技术概述(一看就是架构师写的,对我极其有用)

ETL,是英文 Extract-Transform-Load 的缩写,用来描述将数据从来源端经过抽取(extract)、交互转换(transform)、加载(load)至目的端的过程。ETL一词较常用在数据仓库,但其对象并不限于...

gulf ⋅ 24分钟前 ⋅ 0

redis在windows环境的后台运行方法

在后台运行,首先需要安装redis服务,命令为 redis-server.exe --service-install redis.windows.conf --loglevel verbose 启动,命令为 redis-server --service-start 停止,命令为 redis-...

程序羊 ⋅ 26分钟前 ⋅ 0

比特币现金开发者提出新的交易订单规则

本周,四位比特币现金的四位开发者和研究员:Joannes Vermorel(Lokad),AmaurySéchet(比特币ABC),Shammah Chancellor(比特币ABC)和Tomas van der Wansem(Bitcrust)共同发表了一篇关...

lpy411 ⋅ 29分钟前 ⋅ 0

vue获取input输入框的数据

用惯了jQuery,突然使用vue感觉很不习惯,有很多不同的地方,感觉是两个不同的思想来写前端的代码。jQuery是使用选择器($)选取DOM对象,对其进行赋值、取值、事件绑定等操作。而Vue则是通过...

王子城 ⋅ 31分钟前 ⋅ 0

竟然这就是面向对象的游戏设计?!

从程序角度考虑,许多 JavaScript 都基于循环和大量的 if/else 语句。在本文中,我们可了解一种更聪明的做法 — 在 JavaScript 游戏中使用面向对象来设计。本文将概述原型继承和使用 JavaSc...

柳猫 ⋅ 36分钟前 ⋅ 2

git cmd git bash

刚用到了Git,看到windows环境下有两个命令输入窗口 第一个是可视化图形界面,第二个是CMD,第三个是Bash。 Git中的Bash是基于CMD的,在CMD的基础上增添一些新的命令与功能。所以建议在使用的...

东东笔记 ⋅ 39分钟前 ⋅ 0

分布式系统CAP和Base

1、分布式系统 1.1 简介 由多台计算机和通信的软件组件通过计算机网络连接(本地网络或广域网)组成。分布式系统是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的...

xixingzhe ⋅ 49分钟前 ⋅ 0

查看磁盘占用情况

记一次jenkins构建失败的问题 Build step 'Send build artifacts over SSH' changed build result to UNSTABLE 网上查资料都没明确表明是什么错,回忆之前处理这样的问题。第一时间想到的是不...

ManderSF ⋅ 51分钟前 ⋅ 0

数据库管理提速:SQL解析的探索与应用

前言: SQL解析是一项复杂的技术,一般都是由数据库厂商来掌握,当然也有公司专门提供SQL解析的API。SQL解析与优化是属于编译器范畴,和C语言等其他语言的解析没有本质的区别。其中分为词法分...

java高级架构牛人 ⋅ 58分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部