JVM实战(2)-jstack和线程dump分析

本文详细讲解了Java线程死锁与热锁的原理、分析方法,通过jstack命令实例演示,以及如何结合操作系统工具和代码优化解决这类问题。重点探讨了死锁的形成机制、jstack在死锁诊断中的角色,以及如何通过堆栈信息排查热锁性能瓶颈。


一、几个概念:
1、jstack命令的语法格式:jstack <pid>,可用于查看java进程id。

2、Dump文件:Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。Dump文件是用来给驱动程序编写人员调试驱动程序用的,这种文件必须用专门的工具软件打开,比如使用Windbg。

在Windbg中可以通过.dump命令保存进程的dump文件。比如下面的命令把当前进程的镜像保存为c:\testdump.dmp文件:.dump /ma c:\testdump.dmp。

其中、ma参数表示dump文件应该包含进程的完整信息,包括整个用户态的内存,这样dump文件尺寸会比较大,信息非常全面。如果不是用、ma参数,保存下来的dump文件只包含了部分重要资料,比如寄存器和线程栈空间,文件尺寸会比较小,无法分析所有的数据。

3、java线程Dump:线程dump是非常有用的诊断java应用问题的工具,每一个java虚拟机都有及时生成显示所有线程在某一点状态的线程dump的能力。虽然各个java虚拟机线程dump打印输出格式上略微有一些不同,但是线程dump出来的信息包含线程基本信息;线程的运行状态、标识和调用的堆栈;调用的堆栈包含完整的类名,所执行的方法,如果可能的话还有源代码的行数。

JVM中的许多问题都可以使用线程dump文件来进行诊断,其中比较典型的包括线程阻塞,CPU使用率过高,JVM Crash,堆内存不足和类装载等问题。

一般情况下,通过jstack输出的线程信息主要包括:jvm自身线程、用户线程等。其中jvm线程会在jvm启动时就会存在。对于用户线程则是在用户访问时才会生成。

jstack PID

这里写图片描述
或者可以使用一下命令将线程信息存入文件中:#jstack -l pid > jstack.log

其中标注daemon字样的是后台线程。以上的用户线程中包括:

1、线程的一些基本信息:名称、优先级及id
2、线程状态:waiting on condition等
3、线程的调用栈
4、线程锁住的资源:locked<0x3f63d600>


二、Monitor(监视器)
  在多线程的java程序中,实现线程之间的同步,就要说说Monitor。Monitor是java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者Class的锁。每一个对象都有,也仅有一个Monitor。下面这个图描述了线程和Monitor之间的关系,以及线程的状态转换图:
这里写图片描述

1、进入区(Entry Set):表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则进入拥有者;否则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。
2、拥有者(The Owner):表示某一线程成功竞争到对象锁。
3、等待区(Wait Set):表示线程通过对象的wait方法释放对象的锁,并在等待区等待被唤醒。

  从图中可以看出,一个 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){
        ……
}

三、调用修饰:
  表示线程在方法调用时额外的重要操作。线程dump分析的重要信息。修饰上方的方法调用。

1、locked<地址>目标:使用synchronized申请对象锁成功,监视器的拥有者;
2、waiting to lock<地址>目标:使用synchronized申请对象锁未成功,在进入区等待;
3、waiting on<地址>目标:使用synchronized申请对象锁成功后,调用了wait方法,进入对象的等待区等待。在调用栈顶出线,线程状态为WAITING或TIMED_WAITING;
4、parking to wait for<地址>目标:park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包出现的新的机制,与synchronized体系不同;

四、线程状态
  想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态,下面这些状态是我们使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态:

1、NEW,未启动的。不会出现在Dump中。
2、RUNNABLE,在虚拟机内执行的。
3、BLOCKED,受阻塞并等待监视器锁。
4、WATING,无限期等待另一个线程执行特定操作。
5、TIMED_WATING,有时限的等待另一个线程的特定操作。
6、TERMINATED,已退出的。

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

五、线程动作:
线程状态产生的原因:

1、runnable:状态一般为RUNNABLE,表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。
2、in Object.wait():等待区等待,状态为WAITING或TIMED_WAITING。
3、waiting for monitor entry:进入区等待,状态为BLOCKED。
4、waiting on condition:等待去等待,被park。
5、sleeping:休眠的线程,调用了Thread.sleep()。

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

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

1. "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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值