JAVA排查工具的使用

我们最先接触的排查问题的方法是DEBUG和异常报错信息,已能解决我们开发时大多数的情况。但有时问题并不是我们代码的问题,定位这些问题则需要jvm提供的工具。

排查工具

jstack

介绍

jstack命令工具可以得到线程堆栈信息,方便分析。

有什么用?

  1. 可以检测出死锁
  2. 分析线程的状态,观察那里出现了阻塞

使用方法

前置知识

JAVA排查工具的使用_第1张图片

  1. jstack中线程的状态:

    • NEW(新建状态) 新创建了一个线程对象。
    • RUNNABLE(运行态) 线程对象创建后,其他线程调用了该对象的start()方法。分就绪态和运行态
    • BLOCKED(阻塞状态) 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
    • WAITING(等待态) 线程处于等待态表示它需要等待其他线程的指示才能继续运行
    • TIMED_WAITING(超时等待态) 当线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态。与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
    • TERMINATED(终止态) 线程执行结束后的状态。
  2. 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 >),就可以顺藤摸瓜了)
  3. 日志样例

    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排查命令:

  1. top 命令:查看哪个进程占用 CPU 过高。定位到 pid
  2. top -Hp pid 命令:查看问题进程中的线程情况。
  3. jstack pid | grep nid -C10 :查看对应的线程前后 10 行的状态信息(注意,先使用 printf '%x\n' nid 或者其他方式,将十进制的 nid 转换为十六进制的 nid

在线定位(Arthas 工具)

Arthas 是阿里开源的 Java 诊断工具。

  1. 先下载 arthas-boot.jar 包,直接通过 java -jar 命令启动,然后会让列出所有正在运行的 java 进程,让用户选择需要监控的进程,之后会进入 Arthas 的操作界面
  2. 在操作界面输入 dashboard 命令,可以看到所监控的进程的所有线程信息(线程 ID、名称、状态、占用 CPU 情况、占用内存情况、是否为守护线程等等)、内存信息(堆内存、Eden 区、Survivor 区、老年代、方法区)、以及机器情况。
  3. 通过 sysporp 命令可以查看所有的 System Properties 信息。
  4. 通过 sysenv 命令可以查看所有的环境变量信息。
  5. JVM 命令,查看当前的进程使用的 JVM 参数。
  6. 在知道了线程 id 之后,可以通过 thread thread_id 的方式查看某个线程正在执行的堆栈信息。
  7. 通过 sc 命令可以查看已经加载过的类的信息。
  8. 通过 sm 类名的方式可以获取类的所有函数,添加 -d 可以获取详细的函数信息,也可以指定查看某个函数
  9. 通过 jad 命令可以进行反编译代码,该功能可以帮助我们查看动态代理生成了什么样的类(例如先通过 sc + 通配的方式定位到某个类,再通过 jad 命令反编译得出该类的代码)
  10. 通过 watch 命令可以查看当前函数的参数/返回值/异常信息
  11. ognl命令,用于动态执行代码,在当前线程环境中执行代码。( ognl '@[email protected]("hello ognl")'
  12. 使用 redefine /path/xxx.class 的方式,能重新加载编译好的类。通常可以很方便的实现一些热修复。其实和 IDEA 中 tomcat 的热部署原理一样,就是用 ClasssLoader 重新 load 一遍修改的类。

你可能感兴趣的:(java)