11.9.jstack使用详解及定位死锁问题

有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增 高了、出现了死锁、死循环等,我们该如何分析呢?

由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要 看下jvm的内部线程的执行情况,然后再进行分析查找出原因。

这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进 行快照,并且打印出来 :

#用法:jstack <pid>
[root@myshop02 ~]# jstack 3694
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):

"Attach Listener" #32 daemon prio=9 os_prio=0 tid=0x00007fed14008800 nid=0x138c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Catalina-utility-2" #31 prio=1 os_prio=0 tid=0x00007fed2005c000 nid=0xe91 waiting on condition [0x00007fed0cfc9000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000c030dbc8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

......

1、线程的状态

由上图可知,在Java中线程的状态一共被分成6种:

1)、初始态(NEW)

创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。

2)、运行态(RUNNABLE)

在Java中,运行态包括 就绪态和运行态。

就绪态:

该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运 行。

所有就绪态的线程存放在就绪队列中。

运行态:

获得CPU执行权,正在执行的线程。

由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条 运行态的线程。

3)、阻塞态(BLOCKED)

当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。

而在Java中,阻塞态专指请求锁失败时进入的状态。

由一个阻塞队列存放所有阻塞态的线程。

处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。

4)、等待态(WAITING)

当前线程中调用wait、join、park函数时,当前线程就会进入等待态。

也有一个等待队列存放所有等待态的线程。

线程处于等待态表示它需要等待其他线程的指示才能继续运行。

进入等待态的线程会释放CPU执行权,并释放资源(如:锁)

5)、超时等待态(TIMED_WAITING)

当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就 会进入该状态;

它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其 他线程唤醒;

进入该状态后释放CPU执行权 和 占有的资源。

与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。

6)、终止态(TERMINATED)

线程执行结束后的状态。

2、实战:死锁问题

如果在生产环境发生了死锁,我们将看到的是部署的程序没有任何反应了,这个时候我们可以借助jstack进行分析,下面我们实战下查找死锁的原因。

  • 构造死锁

    思路:两个线程thread1,thread2,thread1拥有obj1锁,thread2拥有obj2锁。此时thread1想要获取obj2锁,thread2也想要获取obj1锁,于是出现死锁。

    代码如下:

    public class TestDeadLock {
        private static Object obj1 = new Object();
        private static Object obj2 = new Object();
    
        public static void main(String[] args) {
            new Thread(new Thread1()).start();
            new Thread(new Thread2()).start();
    
        }
    
        private static class Thread1 implements Runnable {
    
            @Override
            public void run() {
                synchronized (obj1) {
                    try {
                        System.out.println(">>>>>>>>>>>" + Thread.currentThread().getName() + "获取了obj1锁");
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (obj2) {
                        System.out.println(">>>>>>>>>>>" + Thread.currentThread().getName() + "获取了obj2锁");
                    }
                }
            }
        }
    
        private static class Thread2 implements Runnable {
    
            @Override
            public void run() {
                synchronized (obj2) {
                    try {
                        System.out.println(">>>>>>>>>>>" + Thread.currentThread().getName() + "获取了obj2锁");
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (obj1) {
                        System.out.println(">>>>>>>>>>>" + Thread.currentThread().getName() + "获取了obj1锁");
                    }
                }
    
            }
        }
    }
  • 编码测试

    E:\dev_env\jdk8\bin\java "-javaagent:E:\smart_an1\devtool\JetBrains\IntelliJ IDEA 2018.1\lib\idea_rt.jar=20149:E:\smart_an1\devtool\JetBrains\IntelliJ IDEA 2018.1\bin" -Dfile.encoding=UTF-8 -classpath E:\dev_env\jdk8\jre\lib\charsets.jar;E:\dev_env\jdk8\jre\lib\deploy.jar;E:\dev_env\jdk8\jre\lib\ext\access-bridge-64.jar;E:\dev_env\jdk8\jre\lib\ext\cldrdata.jar;E:\dev_env\jdk8\jre\lib\ext\dnsns.jar;E:\dev_env\jdk8\jre\lib\ext\jaccess.jar;E:\dev_env\jdk8\jre\lib\ext\jfxrt.jar;E:\dev_env\jdk8\jre\lib\ext\localedata.jar;E:\dev_env\jdk8\jre\lib\ext\nashorn.jar;E:\dev_env\jdk8\jre\lib\ext\sunec.jar;E:\dev_env\jdk8\jre\lib\ext\sunjce_provider.jar;E:\dev_env\jdk8\jre\lib\ext\sunmscapi.jar;E:\dev_env\jdk8\jre\lib\ext\sunpkcs11.jar;E:\dev_env\jdk8\jre\lib\ext\zipfs.jar;E:\dev_env\jdk8\jre\lib\javaws.jar;E:\dev_env\jdk8\jre\lib\jce.jar;E:\dev_env\jdk8\jre\lib\jfr.jar;E:\dev_env\jdk8\jre\lib\jfxswt.jar;E:\dev_env\jdk8\jre\lib\jsse.jar;E:\dev_env\jdk8\jre\lib\management-agent.jar;E:\dev_env\jdk8\jre\lib\plugin.jar;E:\dev_env\jdk8\jre\lib\resources.jar;E:\dev_env\jdk8\jre\lib\rt.jar;E:\dev_env\IdeaProjects\performance-tuning\out\production\oom com.zte.deadlock.TestDeadLock
    >>>>>>>>>>>Thread-0获取了obj1锁
    >>>>>>>>>>>Thread-1获取了obj2锁
    //程序卡死在这里,互相等待锁
  • 使用jstack进行分析

    使用jstack命令查看死锁结果并分析如下:

    在输出的信息中,已经看到,发现了1个死锁,关键看这个

Last updated

Was this helpful?