JVM 命令指北(4)-jstack 命令

1. 功能简介

jstack 用于打印出给定的 Java进程ID 或远程调试服务的线程快照。线程快照是 Java 虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环造成的长时间等待。 通过 jstack 查看各个线程的调用堆栈,就可以知道停顿的线程到底在做什么,或者等待什么资源。 如果 Java 程序崩溃生成core文件jstack工具也可以获取 core 文件的 Java方法栈和本地方法栈的信息,从而可以直达事故现场分析出程序是如何崩溃的。另外需注意机器上当前登录用户和 Java 进程启动用户不是同一个时,无法使用该命令

  • 需要注意的问题
    不同的 JAVA 虚机的线程 DUMP 的创建方法和文件格式是不一样的,不同的 JVM 版本, dump信息也有差别。在实际运行中,往往一次 dump的信息,还不足以确认问题。建议产生三次 dump信息,如果每次 dump都指向同一个问题,才能确定问题的典型性

2. 使用方式

命令格式 参数说明
jstack [ option ] pid pid 为 Java 应用程序的进程号,连接到正在运行的进程
jstack [ option ] executable core executable 为产生core dump的 java可执行程序,core为打印出的core文件,该命令可连接到核心文件
jstack [ option ] [ server-id@]remote-hostname-or-IP remote-hostname-or-ip 是远程debug服务器的名称或IP,server-id是唯一id,假如一台主机上有多个远程debug服务会需求区分
Options 功能
-F 当线程没有响应的时候强制打印栈信息
-m 打印 Java 和 native c/c++ 框架的所有栈信息
-l 长列表. 打印关于锁的附加信息,例如属于 java.util.concurrent 的 ownable synchronizers 列表

常见的使用方式如下

// 打印指定 Java 进程的栈信息
jstack -l 44295
// 输出指定 Java 进程的栈信息到 log 文件中
jstack -l 44295 > ~/44295.log

3. 分析注意

3.1 线程状态

通过 jstack 命令来分析线程的情况,首先要知道线程都有哪些状态,以下状态是查看线程堆栈信息时可能会看到的:

RUNNABLE: 在虚拟机内执行的
BLOCKED: 受阻塞并等待监视器锁,通常是多条线程争夺 synchronized 锁住资源失败触发
WATING:无限期等待另一个线程执行特定操作
TIMED_WATING:有时限的等待另一个线程的特定操作,通常由 sleep(n) 及定时任务触发
TERMINATED:已退出的
在这里插入图片描述

3.2 线程信息的格式

参考 Java 对象锁的 Monitor 实现,可以知道一个 Monitor 在某个时刻,只能被一个线程拥有,拥有锁的线程就是 Active Thread,而其它线程都是 Waiting Thread,分别在两个队列 EntrySetWaitSet 里面等待。在 EntrySet中等待的线程状态是 Waiting for monitor entry,表示等待获取锁;而在 WaitSet中等待的线程状态是 in Object.wait(),表示获得锁成功后又释放,等待被唤醒

一个典型线程信息如下,可以看到在第二行中标出的是线程状态 WAITING (on object monitor),表示在等待获取 Monitor 对象,而第一行的in Object.wait() 则是等待状态产生的原因,可以知道是线程当前执行条件不满足,自己调用了 wait() 方法放弃锁,等待被 notify()。而waiting on <no object reference available> 则是当前线程对 Monitor 锁的操作状态,表示使用synchronized申请对象锁成功后,释放锁并在WaitSet 里面等待被唤醒

“Disposer” #105 daemon prio=10 os_prio=31 cpu=35.51ms elapsed=241580.14s tid=0x00007fa74fb0f800 nid=0x20e87 in Object.wait() [0x000070000a46c000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait([email protected]/Native Method)
- waiting on <no object reference available>
at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:155)
- waiting to re-lock in wait() <0x00000007493fc428> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:176)
at com.sun.webkit.Disposer.run([email protected]/Disposer.java:122)
at java.lang.Thread.run([email protected]/Thread.java:834)
.
Locked ownable synchronizers:
- None

线程对 Monitor 对象的操作状态 意义
locked <地址> 目标 使用synchronized申请对象锁成功,监视器的拥有者
waiting to lock <地址> 目标 使用synchronized申请对象锁未成功,在EntrySet 队列等待
waiting on <地址> 目标 使用synchronized申请对象锁成功后,释放锁在WaitSet 区等待
parking to wait for <地址> 目标 等待获取锁时调用 park() 挂起线程
线程状态描述 线程状态
runnable 状态一般为RUNNABLE
in Object.wait() WaitSet等待区等待,状态为 WAITING 或 TIMED_WAITING
waiting for monitor entry EntrySet 进入区等待,状态为 BLOCKED
waiting on condition WaitSet等待区等待,线程被 park
sleeping 休眠的线程,调用了Thread.sleep()

3.3 重点信息

搜索关键字 含义
Deadlock 死锁,重点关注
Waiting on condition 等待资源,重点关注
Waiting on monitor entry 等待获取监视器,重点关注
Blocked 阻塞,重点关注
  1. Deadlock关键字
    这个一般指多个线程调用时,自己占用了一部分资源,又需要请求对方占用的资源导致无限期等待对方释放资源的情况。这种情况堆栈写的会很明显,它会打印 Found one Java-level deadlock,然后指出造成死锁的线程的信息

  2. Blocked 关键字
    线程状态是 Blocked,说明线程等待资源超时。此时可查看其 waiting to lock <0x0000000xxxxxxx>信息,在 dump 日志里查找这个地址字符串,如发现有大量线程都在等待给这个地址上锁,可能在代码级别已经存在冲突的调用。此时如果能在日志里找到谁获得了这个锁(关键字locked <0x0000000xxxxxxx>),则可以有效找到事故现场

  3. Wait on condition 关键字
    该状态表示等待资源,或等待某个条件的发生,具体原因需结合堆栈信息来分析。最常见的情况就是线程处于sleep状态,等待被唤醒

    • 如果线程是用户线程,则证明该线程正在等待资源。一般是大量读取某资源,且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取
    • 如果发现有大量的线程都在处在 Wait on condition,从线程 stack 看正等待网络读写,这可能是一个网络瓶颈的征兆,因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常到达
  4. 持续运行的IO
    IO 操作可以在 RUNNABLE状态达成阻塞,例如数据库死锁、网络读写。 需要格外注意对 IO 线程真实状态的分析,比如readLine()造成的 IO 阻塞,一般被捕捉到RUNNABLEIO 调用都是有问题的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章