jstack 命令

  • 什么是jstack
  • jstack命令
  • jstack实战操作


什么是jstack

jstack是用于生成java虚拟机当前时刻的线程快照。线程快找是当前java虚拟机内存每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因

如果出现死锁,死循环,请求外部资源出现长时间等待等,线程出现停顿的时候,通过jstack来查看各个线程调用堆栈,就知道线程在后台做了什么,或者等待什么资源,如果java程序崩溃生成core文件,jstack工具可以用来获取core文件的java,stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程

程序触发生成的问题,另外,jstack工具还可以附属到正在运行的java程序中看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现bug的状态,jstack是非常有用的。

jstack命令

Java 调用堆栈 在哪里 javajstack堆栈分析_Java 调用堆栈 在哪里


参数说明:

  • -F 当 ‘jstack [-l] pid’ 没有响应的时候强制打印栈信息,如果直接jstack无响应时,用于强制jstack,一般情况下无需使用
  • -l 长列表 打印关于锁定附加信息,例如属于:java.util.concurrent的ownable synchronizers列表会使jvm停顿的长久的多,
  • -m 打印java和native c/c++ 框架的所有栈信息.可以打印JVM的堆栈,显示上Native的栈帧,一般应用排查不需要使用

执行命令

jstack -m 12905

Java 调用堆栈 在哪里 javajstack堆栈分析_死锁_02


科普一下线程状态:

NEW

未启动的。不会出现在Dump中

RUNNABLE

在虚拟机内执行的

BLOCKED

受阻塞并等待监视器锁

WATING

无限期等待另一个线程执行特定操作。

TIMED_WATING

有时限的等待另一个线程的特定操作

TERMINATED

已退出的

jstack实战操作

现在假设一个场景,在几乎没有什么调用的情况下,服务器的cpu的使用率一直居高不下非常有可能是程序中出现死循环导致的,当然在业务量不大的情况下,如果cpu占用率过高,除了死循环以外,常见的还有,内存泄漏,导致大量的full GC

jstack实战之cpu 占用过高-----测试代码

/**
 * @Auther: corey
 * @Date: 2020/8/5 14:25
 * @Description: jstack 测试代码
 */

public class JstackTest {
 private static Executor executor = Executors.newFixedThreadPool(5);
    private static final Object lock = new Object();
    public static void main(String[] args) {
        MyRunnableImpl myRunnable = new MyRunnableImpl();
        MyRunnableImpl myRunnable1 = new MyRunnableImpl();
        executor.execute(myRunnable);
        executor.execute(myRunnable1);
    }
    static class MyRunnableImpl implements Runnable{

        @Override
        public void run() {
            synchronized (lock){
                //死循环
                calculate();
            }
        }
        private void calculate(){
            int i = 0;
            while (true){
                i++;
            }
        }
    }
}

Java 调用堆栈 在哪里 javajstack堆栈分析_Java 调用堆栈 在哪里_03


使用top命令可以看到,cpu的占用到99%了,使用 jps命令查看当前java 程序运行的有哪些。

Java 调用堆栈 在哪里 javajstack堆栈分析_java_04


然后使用 jstack 4275 命令查看当前程序出现死循环的是在哪一行

Java 调用堆栈 在哪里 javajstack堆栈分析_java_05


如下上图,JstackTest 程序的 31行

Java 调用堆栈 在哪里 javajstack堆栈分析_Java 调用堆栈 在哪里_06


①线程名。

②线程优先级。

③一个地址(该地址是什么地址,存疑)。

④线程十六进制id。

⑤线程状态。

⑥线程当前所处方法。

⑦该箭头表示线程的执行历程。

jstack实战之死锁线程的定位-----测试代码

/**
 * @Auther: corey
 * @Date: 2020/8/5 15:01
 * @Description:死锁线程的定位
 */
public class JstackLockTest {

    private static Executor executor = Executors.newFixedThreadPool(5);

    private static final Object lockA = new Object();

    private static final Object lockB = new Object();

    public static void main(String[] args) {
        MyRunnableImplOne th1 = new MyRunnableImplOne();
        MyRunnableImplTwo th2 = new MyRunnableImplTwo();

        executor.execute(th1);
        executor.execute(th2);
    }

    static class MyRunnableImplOne implements Runnable {

        @Override
        public void run() {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "获得了锁LockA");
                //循环
                int i = 10;
                while (i > 10) {
                    i--;
                }
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "获得了锁B");
                }
            }
        }
    }

    static class MyRunnableImplTwo implements Runnable {
        @Override
        public void run() {
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName()+"获得了锁lockB");
                int i = 10;
                while (i > 10) {
                    i--;
                }
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName()+"获得了锁lockA");
                }
            }
        }
    }
}

输出如下:

Java 调用堆栈 在哪里 javajstack堆栈分析_死锁_07


如上图所示没有继续输出;继续排查

  • 1 使用 jps 命令查看

    使用jstack pid指令,查看指定进程的堆栈信息,观察并定位到死锁线程。

    上图可以看到,线程 pool-1-thread-2已经获取到了地址 <0x000000076ae309a0> 正在 java.lang.Thread.State: BLOCKED 等待获取地址为 <0x000000076ae309b0> 的对象锁,

第二个如图所示:
pool-1-thread-1 已经获取到了地址为<0x000000076ae309b0>的地址,正在BLOCKED等待地址为 <0x000000076ae309a0>的对象锁

上述分析过程是对死锁和cpu 超高的情况下的场景分析,我们可以通过诸如【统一获取锁的顺序(线程按照一定的顺序加锁)】、【设置获取锁的超时时间(线程尝试获, 取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)】、【死锁检测】等手段预防死锁发生。但是如果一旦发生了死锁就没法解开了,只能停掉程序,修复bug后再启动服务了。