乐趣区

关于java:JAVA排查工具的使用

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

排查工具

jstack

介绍

jstack 命令工具能够失去线程堆栈信息,不便剖析。

有什么用?

  1. 能够检测出死锁
  2. 剖析线程的状态,察看那里呈现了阻塞

应用办法

前置常识

  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 '@java.lang.System@out.println("hello ognl")'
  12. 应用 redefine /path/xxx.class 的形式,能从新加载编译好的类。通常能够很不便的实现一些热修复。其实和 IDEA 中 tomcat 的热部署原理一样,就是用 ClasssLoader 从新 load 一遍批改的类。
退出移动版