java线程 dump (java-core)分析

1.如何获取线程 dump (java-core)文件

#1.jstack
jstack -l  <pid> >> <file-path>
如: jstack -l 37320 > /opt/tmp/threadDump.txt

#2.kill -3
kill -3 <pid>

#3.JVisualVM图形工具采集

注意:
>> 在实际运行中,往往一次 dump的信息,还不足以确认问题。
建议多次 dump,寻找其中的共性与不同点。 

分析工具参考: https://www.jianshu.com/p/200416bc3964

2.线程分析

2.1 线程状态分析

2.1.1 Runnable

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

一般情况下处于运行状态线程是会消耗CPU的,但不是所有的RUNNABLE都会消耗CPU,
比如线程进行网络IO时,这时线程状态是挂起的,但由于挂起发生在本地代码,虚拟机并不感知,
所以不会像显示调用Java的sleep()或者wait()等方法进入WAITING状态,只有等数据到来时才消耗一点CPU.

2.1.2 BLOCKED

此时的线程处于阻塞状态,一般是在等待进入一个临界区“waiting for monitor entry”,这种状态是需要重点关注的.

###Waiting for monitor entry
在多线程的 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,执行临界区的代码. 此时为Runnable.
>> 该 monitor 被其它线程拥有,本线程在 Entry Set队列中等待。 此时为BLOCKED.

#BLOCKED状态下, 对应的堆栈示例:
 "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)的设置,是为了保证其内部的代码执行的原子性和完整性。
但是因为临界区在任何时间只允许线程串行通过,这 和我们多线程的程序的初衷是相反的。 
如果在多线程的程序中,大量使用 synchronized,或者不适当的使用了它,
会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。
如果在线程 DUMP中发现了这个情况,应该审查源码,改进程序。

2.1.3 TIMED_WAITING/WATING

表示线程被挂起,必须等待notify()或notifyAll()或unpark()或接收到interrupt信号才能退出等待状态.
>> 当设置超时时间时状态为TIMED_WAITING;
>> 如果是未设置超时时间,这时的状态为WATING.

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

###2.in Object.wait() / on object monitor: 
说明该线程正在执行obj.wait()方法,放弃了 Monitor,进入 “Wait Set”队列. 那么线程为什么会进入 “Wait Set” 呢? 
当线程获得了 Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,
它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。
只有当别的线程在该对象上调用了 notify() 或者 notifyAll() , “ Wait Set”队列中线程才得到机会去竞争,
但是只有一个线程获得对象的 Monitor,恢复到运行态。

在 “Wait Set”中的线程, DUMP中表现为: in Object.wait(),类似于:
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000002d34800 nid=0x2b50 in Object.wait() [0x0000000018adf000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d5f06b68> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x00000000d5f06b68> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

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

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

2.1.x 阻塞和等待的区别

#阻塞状态的线程:
是在等待一个排它锁,直到别的线程释放该排它锁,该线程获取到该锁才能退出阻塞状态;
堆栈信息显示为waiting for monitor entry, 在Entry Set中.
即代码尚未走进synchronized块.
synchronized(obj) {  
  ...
}

#等待状态的线程:
则是等待一段时间,由系统唤醒或者别的线程唤醒,该线程便退出等待状态。
堆栈信息显示为 in object.wait() / on object monitor, 在Wait Set 中.
即代码走到了下文的 obj.wait();
synchronized(obj) {  
  ...
  obj.wait();  
  ...
}

2.2 JDK 5.0 的 Lock

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

https://www.jianshu.com/p/8a4a519e2f13 (可见此文 4.反例)

2.3 热锁

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

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

2.4 哪些线程状态占用CPU?

处于TIMED_WAITING、WATING、BLOCKED状态的线程是不消耗CPU的,
而处于 RUNNABLE 状态的线程要结合当前线程代码的性质判断是否消耗CPU:
>> 纯java运算代码,并且未被挂起,是消耗CPU的;
>> 网络IO操作,在等待数据时是不消耗CPU的;

参考资料
https://docs.oracle.com/cd/E15289_01/JRJDK/using_threaddumps.htm (Oracle官网)
https://www.iteye.com/blog/jameswxx-1041173
https://www.cnblogs.com/yuandengta/p/12900608.html (深入分析Object.notify/wait机制)
https://www.cnblogs.com/perfma/p/12515665.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容