文章目录

  • jstack命令
  • 基础知识:java线程状态
  • blocked状态和waiting状态区别
  • thread dump文件分析
  • 重点关注1:程序死锁DeadLock
  • 重点关注2:waiting on condition
  • 重点关注3:Blocked线程阻塞
  • 重点关注4:Waiting for monitor entry 和 in Object.wait():
  • thread dump文件示例
  • 实例1:Waiting for monitor entry 和 Blocked
  • 实例2:Waiting on condition 和 TIMED_WAITING
  • 实例3:in Object.wait() 和 TIMED_WAITING


jstack命令

生成thread dump文件需要使用jstack命令
官方说明:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstack.html#BABGJDIF

jstack pid > threaddump.txt

如果发生> Unable to open socket file: target process not responding or HotSpot VM not loaded
需要check启动java服务的用户和当前导出dump文件的用户是否一致

拿到dump文件之后,如何查找线上thread对应的dump文件中的thread呢?

top -p pid -H 就显示了该pid进程下的所有线程 所占cpu的大小

通过printf “%x” 10进制线程 就显示了其16进制的线程id

然后到threaddump.txt中查找这个16进制线程的id,就可以了

dump java 线程分析 java获取线程dump文件_java

基础知识:java线程状态

java线程状态

  • new
  • runnable
  • timed_waiting
  • waiting
  • blocked
  • terminated

blocked状态和waiting状态区别

Blocked 指的是一个线程因为等待临界区的锁(Lock 或者 synchronized 关键字)而被阻塞的状态,请你注意的是处于这个状态的线程还没有拿到锁

Waiting 指的是一个线程拿到了锁,但是需要等待其他线程执行某些操作。比如调用了 Object.wait、Thread.join 或者 LockSupport.park 方法时,进入 Waiting 状态。前提是这个线程已经拿到锁了,并且在进入 Waiting 状态前,操作系统层面会自动释放锁,当等待条件满足,外部调用了 Object.notify 或者 LockSupport.unpark 方法,线程会重新竞争锁,成功获得锁后才能进入到 Runnable 状态继续执行。

参考后面的entry set,和wait set部分

thread dump文件分析

jstack dump文件里,值得关注的线程状态有:

  • 死锁,Deadlock(重点关注)
  • 执行中,Runnable
  • 等待资源,Waiting on condition(重点关注)
  • 等待获取监视器,Waiting on monitor entry(重点关注)
  • 暂停,Suspended
  • 对象等待中,Object.wait() 或 TIMED_WAITING
  • 阻塞,Blocked(重点关注)
  • 停止,Parked

重点关注1:程序死锁DeadLock

  1. AB-BA问题

  2. 内存不足

    两个线程T1和T2,执行某一个任务,其中T1已经获取10M内存,T2获取20M内存,如果每个线程执行单元都需要30M内存,但是剩余可用的内存刚好为20M,那么两个线程都在等待对方释放资源

  3. 一问一答的数据交换

    服务端错过了回答,结果客户端一直在等待回答

  4. 数据库锁

    比如某个线程执行for update语句退出了事务,其他线程访问该数据库时都将陷入死锁

  5. 文件锁

    某线程获得了文件锁意外退出,其他读取该文件的线程也将会进入死锁直到系统释放文件句柄资源

  6. 死循环引起的死锁

重点关注2:waiting on condition

Waiting on condition:等待资源,或等待某个条件的发生。具体原因需结合 stacktrace来分析。

  • 如果堆栈信息明确是应用代码,则证明该线程正在等待资源。一般是大量读取某资源,且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取。
  • 又或者,正在等待其他线程的执行等。
  • 如果发现有大量的线程都在处在 Waiting on condition,从线程stack看,正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。
    • 一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写;
    • 另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。
  • 另外一种出现 Waiting on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。

重点关注3:Blocked线程阻塞

比如我们较常见的 BLOCKED状态,一般都是在请求锁,在请求资源之类的。比如多线程操作数据库,一个耗时较多的操作,会导致其他对于库的写入也受影响。synchronized关键字也会使线程进入到Blocked状态

再比如操作系统等限制了可以打开的文件句柄数,如果系统里已经打开达到了阈值,但未进行正确的关闭,此时就会产生问题。

重点关注4:Waiting for monitor entry 和 in Object.wait():

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

从下图1中可以看出,每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”

dump java 线程分析 java获取线程dump文件_dump java 线程分析_02

thread dump文件示例

实例1:Waiting for monitor entry 和 Blocked

dump java 线程分析 java获取线程dump文件_死锁_03


说明:

  1. 线程状态是 Blocked,阻塞状态。说明线程等待资源超时!
  2. “waiting to lock <0x00000000acf4d0c0>”指,线程在等待给这个 0x00000000acf4d0c0 地址上锁(英文可描述为:trying to obtain 0x00000000acf4d0c0 lock)
  3. 在 dump 日志里查找字符串 0x00000000acf4d0c0,发现有大量线程都在等待给这个地址上锁。如果能在日志里找到谁获得了这个锁(如locked < 0x00000000acf4d0c0 >),就可以顺藤摸瓜了
  4. “waiting for monitor entry”说明此线程通过 synchronized(obj) {……} 申请进入了临界区,从而进入了该Obj Monitor的“Entry Set”队列,但该 obj 对应的 monitor 被其他线程拥有,所以本线程在 Entry Set 队列中等待
  5. 第一行里,"RMI TCP Connection(267865)-172.16.5.25"是 Thread Name 。tid指Java Thread id。nid指native线程的id。prio是线程优先级。[0x00007fd4f8684000]是线程栈起始地址

实例2:Waiting on condition 和 TIMED_WAITING

dump java 线程分析 java获取线程dump文件_线程状态_04


说明:

  1. “TIMED_WAITING (parking)”中的 timed_waiting 指等待状态,但这里指定了时间,到达指定的时间后自动退出等待状态;parking指线程处于挂起中。

  2. “waiting on condition”需要与堆栈中的“parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)”结合来看。

    首先,本线程肯定是在等待某个条件的发生,来把自己唤醒。

    其次,SynchronousQueue 并不是一个队列,只是线程之间移交信息的机制,当我们把一个元素放入到 SynchronousQueue 中时必须有另一个线程正在等待接受移交的任务,因此这就是本线程在等待的条件

实例3:in Object.wait() 和 TIMED_WAITING

dump java 线程分析 java获取线程dump文件_java_05


说明:

  1. “TIMED_WAITING (on object monitor)”,对于本例而言,是因为本线程调用了 java.lang.Object.wait(long timeout) 而进入等待状态

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

  3. RMI RenewClean 是 DGCClient 的一部分。DGC 指的是 Distributed GC,即分布式垃圾回收

  4. 请注意,是先 locked <0x00000000aa672478>,后 waiting on <0x00000000aa672478>。之所以先锁再等同一个对象,请看下面它的代码实现

    static private class  Lock { };
    private Lock lock = new Lock();
    public Reference<? extends T> remove(long timeout)
    {
        synchronized (lock) {
            Reference<? extends T> r = reallyPoll();
            if (r != null) return r;
            for (;;) {
                lock.wait(timeout);
                r = reallyPoll();
                ……
           }
    }

    即,线程的执行中,先用 synchronized 获得了这个对象的 Monitor(对应于 locked <0x00000000aa672478> );当执行到 lock.wait(timeout);,线程就放弃了 Monitor 的所有权,进入“Wait Set”队列(对应于 waiting on <0x00000000aa672478> )

  5. 从堆栈信息看,是正在清理 remote references to remote objects ,reference的租约到了,分布式垃圾回收在逐一清理

参考:

怎样了解你的线程在干嘛?

Java线程Dump分析工具–jstack

三个实例演示 Java Thread Dump 日志分析