咱们最先接触的排查问题的办法是 DEBUG 和异样报错信息,已能解决咱们开发时大多数的状况。但有时问题并不是咱们代码的问题,定位这些问题则须要 jvm 提供的工具。
排查工具
jstack
介绍
jstack 命令工具能够失去线程堆栈信息,不便剖析。
有什么用?
- 能够检测出死锁
- 剖析线程的状态,察看那里呈现了阻塞
应用办法
前置常识
-
jstack 中线程的状态:
- NEW(新建状态) 新创建了一个线程对象。
- RUNNABLE(运行态) 线程对象创立后,其余线程调用了该对象的 start() 办法。分就绪态和运行态
- BLOCKED(阻塞状态) 阻塞状态是线程因为某种原因放弃 CPU 使用权,临时进行运行。直到线程进入就绪状态,才有机会转到运行状态。
- WAITING(期待态) 线程处于期待态示意它须要期待其余线程的批示能力持续运行
- TIMED_WAITING(超时期待态) 当线程调用 sleep(time)、wait、join、parkNanos、parkUntil 时,就会进入该状态。与期待态的区别:到了超时工夫后主动进入阻塞队列,开始竞争锁。
- TERMINATED(终止态) 线程执行完结后的状态。
-
jstack 日志中呈现的要害信息
- Deadlock: 示意有死锁
- Wait on condition: 期待某个资源或条件产生来唤醒本人。
- Blocked: 阻塞
- Waiting on monitor entry:在期待获取锁(阐明此线程通过 synchronized(obj) {……} 申请进入了临界区,从而进入了下图 1 中的“Entry Set”队列,但该 obj 对应的 monitor 被其余线程领有,所以本线程在 Entry Set 队列中期待。)
- in Object.wait():获取锁后又执行 obj.wait() 放弃锁
- TIMED_WAITING (parking)”中的 timed_waiting 指期待状态,但这里指定了工夫,达到指定的工夫后主动退出期待状态;parking 指线程处于挂起中。
- waiting to lock <0x00000000acf4d0c0>”指,线程在期待给这个 0x00000000acf4d0c0 地址上锁(如果能在日志里找到谁取得了这个锁(如 locked < 0x00000000acf4d0c0 >),就能够顺藤摸瓜了)
-
日志样例
import java.util.concurrent.TimeUnit; public class MainThread {public static void main(String[] args) {final Thread thread2 = new Thread(){public void run(){synchronized (this){ try{System.out.println(Thread.currentThread()); TimeUnit.SECONDS.sleep(60); }catch (InterruptedException e){e.printStackTrace(); } } } }; thread2.setName("thread2"); thread2.start(); synchronized (thread2){ try{System.out.println(Thread.currentThread()); TimeUnit.SECONDS.sleep(60); } catch (InterruptedException e) {e.printStackTrace(); } } } }
"main" #1 prio=5 os_prio=0 cpu=93.75ms elapsed=30.25s tid=0x0000027193f7e290 nid=0x4d00 waiting on condition [0x000000fe36fff000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@17/Native Method)
at java.lang.Thread.sleep(java.base@17/Thread.java:337)
at java.util.concurrent.TimeUnit.sleep(java.base@17/TimeUnit.java:446)
at com.leecode.test.MainThread.main(MainThread.java:25)
- locked <0x0000000711ae4be8> (a com.leecode.test.MainThread$1)
Locked ownable synchronizers:
- None
"thread2" #16 prio=5 os_prio=0 cpu=0.00ms elapsed=30.16s tid=0x00000271b7e8e020 nid=0x534c waiting for monitor entry [0x000000fe383ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.leecode.test.MainThread$1.run(MainThread.java:11)
- waiting to lock <0x0000000711ae4be8> (a com.leecode.test.MainThread$1)
Locked ownable synchronizers:
- None
- "thread2" 为线程名称,在平时创立线程或线程池时请务必取一个见明之义的线程名称,不便排查问题;- prio=5:线程优先级,不必关怀;- tid=0x00000271b7e8e020:线程 id,不必关怀;- nid=0x534c:操作系统映射的线程 id, 十分要害,前面再应用 jstack 时补充;- waiting for monitor entry:示意线程正在期待获取锁
- 0x000000fe383ff000:线程栈起始地址
剖析:
主线程获取到 thread2 对象上的锁,因而正在执行 sleep 操作,状态为 TIMED_WAINTING, 而 thread2 因为未获取到 thread2 对象上的锁,因而处于 BLOCKED 状态。thread2 正在 "waiting to lock <0x0000000711ae4be8>",即试图在地址为 0x0000000711ae4be8 所在的对象获取锁,而该锁却被 main 线程占有(locked <0x0000000711ae4be8>)。main 线程正在 "waiting on condition",阐明正在期待某个条件触发,由 jstacktrace 来看,此线程正在 sleep。
Linux 排查命令:
- top 命令:查看哪个过程占用 CPU 过高。定位到 pid
- top -Hp pid 命令:查看问题过程中的线程状况。
- jstack pid | grep nid -C10:查看对应的线程前后 10 行的状态信息(留神,先应用
printf '%x\n' nid 或者其余形式,将十进制的 nid 转换为十六进制的 nid
)
在线定位(Arthas 工具)
Arthas 是阿里开源的 Java 诊断工具。
- 先下载 arthas-boot.jar 包,间接通过 java -jar 命令启动,而后会让列出所有正在运行的 java 过程,让用户抉择须要监控的过程,之后会进入 Arthas 的操作界面
- 在操作界面输出 dashboard 命令,能够看到所监控的过程的所有线程信息(线程 ID、名称、状态、占用 CPU 状况、占用内存状况、是否为守护线程等等)、内存信息(堆内存、Eden 区、Survivor 区、老年代、办法区)、以及机器状况。
- 通过 sysporp 命令能够查看所有的 System Properties 信息。
- 通过 sysenv 命令能够查看所有的环境变量信息。
- JVM 命令,查看以后的过程应用的 JVM 参数。
- 在晓得了线程 id 之后,能够通过 thread thread_id 的形式查看某个线程正在执行的堆栈信息。
- 通过 sc 命令能够查看曾经加载过的类的信息。
- 通过 sm 类名的形式能够获取类的所有函数,增加 -d 能够获取具体的函数信息,也能够指定查看某个函数
- 通过 jad 命令能够进行反编译代码,该性能能够帮忙咱们查看动静代理生成了什么样的类(例如先通过 sc + 通配的形式定位到某个类,再通过 jad 命令反编译得出该类的代码)
- 通过 watch 命令能够查看以后函数的参数 / 返回值 / 异样信息
- ognl 命令,用于动静执行代码,在以后线程环境中执行代码。(
ognl '@java.lang.System@out.println("hello ognl")'
) - 应用 redefine /path/xxx.class 的形式,能从新加载编译好的类。通常能够很不便的实现一些热修复。其实和 IDEA 中 tomcat 的热部署原理一样,就是用 ClasssLoader 从新 load 一遍批改的类。