一、简介
jstack全称叫java stack trace,java的堆栈跟踪工具,用于生成java虚拟机当前时刻的线程快照。
功能主要有两个如下
- 分析死锁;
- 分析cpu过高问题。
1.1 命令格式
- jstack [ option ] pid 查看当前时间点,指定进程的dump堆栈信息。
- jstack [ option ] pid > 文件 将当前时间点的指定进程的dump堆栈信息,写入到指定文件中。# 注:若该文件不存在,则会自动生成; 若该文件存在,则会覆盖源文件。
- jstack [ option ] executable core 查看当前时间点,core文件的dump堆栈信息。
- jstack [ option ] [server_id@]<remote server ip or hostname> 查看当前时间点,远程机器的dump堆栈信息。
option 参数如下:
名称 | 说明 |
---|---|
-f | 当正常输出的请求不被响应时,强制输出线程堆栈。 |
-m | 打印java和native c/c++框架的所有栈信息。可以打印jvm的堆栈,以及native的栈帧,一般应用排查不需要使用。 |
-l | 除堆栈外,显示关于锁的附加信息,在发生死锁时可以用jstack -l pid来观察锁持有情况。 |
1.2 获取cpu飙高的线程id
1.2.1 找到cpu飙高的进程
获取各个进程的cpu和内存情况,并且找到cpu飙高的进程id,比如进程id=10843
top
1.2.2 显示java进程的cpu和内存占用情况
top -p 进程id
1.2.3 获取每个线程的cpu和内存占用情况
- 按h
1.2.4 jstack查看线程情况
# 将10进制线程id转为16进制 printf "%x\n" 线程id # 查看线程情况 jstack 进程id | grep -a 10 十六进制的线程id
二、jstack输出内容解析
其中标注daemon字样的是后台线程。
用户线程中包括:
- 线程的一些基本信息:名称、优先级及id
- 线程状态:waiting on condition等
- 线程的调用栈
- 线程锁住的资源:locked<0x3f63d600>
2.1 monitor(监视器)
在多线程的java程序中,实现线程之间的同步,就要说说monitor。
monitor是java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者class的锁。每一个对象都有,也仅有一个monitor。
- 进入区(entry set):表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则进入拥有者;否则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。
- 拥有者(the owner):表示某一线程成功竞争到对象锁。
- 等待区(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){ …… }
2.2 调用修饰
表示线程在方法调用时额外的重要操作。线程dump分析的重要信息。修饰上方的方法调用。
- locked<地址>目标:使用synchronized申请对象锁成功,监视器的拥有者;
- waiting to lock<地址>目标:使用synchronized申请对象锁未成功,在进入区等待;
- waiting on<地址>目标:使用synchronized申请对象锁成功后,调用了wait方法,进入对象的等待区等待。在调用栈顶出线,线程状态为waiting或timed_waiting;
- parking to wait for<地址>目标:park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包出现的新的机制,与synchronized体系不同。
2.3 线程状态
- 死锁,deadlock(重点关注)
- 等待资源,waiting on condition(重点关注)
- 等待获取管程,waiting on monitor entry(点关注)
- 阻塞,blocked(重点关注)
- 执行中,runnable
- 暂停,suspended
- 对象等待中,object.wait() 或 timed_waiting
- 停止,parked
输出信息例如
"thread-1" prio=10 tid=0x08223860 nid=0xa waiting on condition [0xef47a000..0xef47ac38]
at java.lang.thread.sleep(native method)
at testthread.mysleepingthread.method2(mysleepingthread.java:53)
- locked <0xef63d600> (a testthread.mysleepingthread)
at testthread.mysleepingthread.run(mysleepingthread.java:35)
at java.lang.thread.run(thread.java:595) </span>
我们能看到:
- 线程的状态: waiting on condition
- 线程的调用栈
- 线程的当前锁住的资源: <0xef63d600>
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的时间到了时候,将被唤醒。
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”队列。
这时有两种可能性:
1、该 monitor不被其它线程拥有, entry set里面也没有其它等待线程。本线程即成为相应类或者对象的 monitor的 owner,执行临界区的代码
2、该 monitor被其它线程拥有,本线程在 entry set队列中等待。
在第一种情况下,线程将处于 “runnable”的状态,而第二种情况下,线程 dump会显示处于 “waiting for monitor entry”。
临界区的设置,是为了保证其内部的代码执行的原子性和完整性。但是因为临界区在任何时间只允许线程串行通过,这 和我们多线程的程序的初衷是相反的。 如果在多线程的程序中,大量使用 synchronized,或者不适当的使用了它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在线程 dump中发现了这个情况,应该审查源码,改进程序。
现在我们再来看现在线程为什么会进入 “wait set”。当线程获得了 monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 monitor,进入 “wait set”队列。只有当别的线程在该对象上调用了 notify() 或者 notifyall() , “ wait set”队列中线程才得到机会去竞争,但是只有一个线程获得对象的 monitor,恢复到运行态。
在 “wait set”中的线程, dump中表现为: in object.wait(),类似于:仔细观察上面的 dump信息,你会发现它有以下两行:
- - locked <0xef63beb8> (a java.util.arraylist)
- - waiting on <0xef63beb8> (a java.util.arraylist)
线程的执行中,先用 synchronized 获得了这个对象的 monitor(对应于 locked <0xef63beb8> )。当执行到 obj.wait(), 线程即放弃了 monitor的所有权,进入 “wait set”队列(对应于 waiting on <0xef63beb8> )。
往往在你的程序中,会出现多个类似的线程,他们都有相似的 dump信息。这也可能是正常的。比如,在程序中,有多个服务线程,设计成从一个队列里面读取请求数据。这个队列就是 lock以及 waiting on的对象。
当队列为空的时候,这些线程都会在这个队列上等待,直到队列有了数据,这些线程被 notify,当然只有一个线程获得了 lock,继续执行,而其它线程继续等待。
2.4 线程动作
线程状态产生的原因:
runnable
:状态一般为runnable,表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。in object.wait()
:等待区等待,状态为waiting或timed_waiting。waiting for monitor entry
:进入区等待,状态为blocked。waiting on condition
:等待去等待,被park。sleeping
:休眠的线程,调用了thread.sleep()。
wait on condition 该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stacktrace来分析。 最常见的情况就是线程处于sleep状态,等待被唤醒。 常见的情况还有等待网络io:在java引入nio之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。
在 nio里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几 乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的 cpu时间,相对于用户态的 cpu时间比例较高;如果程序运行在 solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论