Java程序员必备:jstack命令解析

前言

若是有一天,你的Java程序长时间停顿,也许是它病了,须要用jstack拍个片子分析分析,才能诊断具体什么病症,是死锁综合征,仍是死循环等其余病症,本文咱们一块儿来学习jstack命令~java

  • jstack 的功能
  • jstack用法
  • 线程状态等基础回顾
  • 实战案例1:jstack 分析死锁
  • 实战案例2:jstack 分析CPU 太高

jstack 的功能

jstack是JVM自带的Java堆栈跟踪工具,它用于打印出给定的java进程ID、core file、远程调试服务的Java堆栈信息.git

jstack prints Java stack traces of Java threads for a given Java process or
core file or a remote debug server. 
复制代码
  • jstack命令用于生成虚拟机当前时刻的线程快照。
  • 线程快照是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的缘由, 如线程间死锁、死循环、请求外部资源致使的长时间等待等问题。
  • 线程出现停顿的时候经过jstack来查看各个线程的调用堆栈,就能够知道没有响应的线程到底在后台作什么事情,或者等待什么资源。
  • 若是java程序崩溃生成core文件,jstack工具能够用来得到core文件的java stack和native stack的信息,从而能够轻松地知道java程序是如何崩溃和在程序何处发生问题。
  • 另外,jstack工具还能够附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 若是如今运行的java程序呈现hung的状态,jstack是很是有用的。

jstack用法

jstack 命令格式以下github

jstack [ option ] pid 
jstack [ option ] executable core 
jstack [ option ] [server-id@]remote-hostname-or-IP 
复制代码
  • executable Java executable from which the core dump was produced.(多是产生core dump的java可执行程序)
  • core 将被打印信息的core dump文件
  • remote-hostname-or-IP 远程debug服务的主机名或ip
  • server-id 惟一id,假如一台主机上多个远程debug服务

最经常使用的是bash

jstack [option] <pid>  // 打印某个进程的堆栈信息
复制代码

option参数说明以下:服务器

选项 做用
-F 当正常输出的请求不被响应时,强制输出线程堆栈
-m 若是调用到本地方法的话,能够显示C/C++的堆栈
-l 除堆栈外,显示关于锁的附加信息,在发生死锁时能够用jstack -l pid来观察锁持有状况

线程状态等基础回顾

线程状态简介

jstack用于生成线程快照的,咱们分析线程的状况,须要复习一下线程状态吧,拿小凳子坐好,复习一下啦~ 网络

Java语言定义了6种线程池状态:多线程

  • New:建立后还没有启动的线程处于这种状态,不会出如今Dump中。
  • RUNNABLE:包括Running和Ready。线程开启start()方法,会进入该状态,在虚拟机内执行的。
  • Waiting:无限的等待另外一个线程的特定操做。
  • Timed Waiting:有时限的等待另外一个线程的特定操做。
  • 阻塞(Blocked):在程序等待进入同步区域的时候,线程将进入这种状态,在等待监视器锁。
  • 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

Dump文件的线程状态通常其实就如下3种:jvm

  • RUNNABLE,线程处于执行中
  • BLOCKED,线程被阻塞
  • WAITING,线程正在等待

Monitor 监视锁

由于Java程序通常都是多线程运行的,Java多线程跟监视锁环环相扣,因此咱们分析线程状态时,也须要回顾一下Monitor监视锁知识。jsp

有关于线程同步关键字Synchronized与监视锁的爱恨情仇,有兴趣的伙伴能够看一下我这篇文章 Synchronized解析——若是你愿意一层一层剥开个人心ide

Monitor的工做原理图以下:

  • 线程想要获取monitor,首先会进入Entry Set队列,它是Waiting Thread,线程状态是Waiting for monitor entry。
  • 当某个线程成功获取对象的monitor后,进入Owner区域,它就是Active Thread。
  • 若是线程调用了wait()方法,则会进入Wait Set队列,它会释放monitor锁,它也是Waiting Thread,线程状态in Object.wait()
  • 若是其余线程调用 notify() / notifyAll() ,会唤醒Wait Set中的某个线程,该线程再次尝试获取monitor锁,成功即进入Owner区域。

Dump 文件分析关注重点

  • runnable,线程处于执行中
  • deadlock,死锁(重点关注)
  • blocked,线程被阻塞 (重点关注)
  • Parked,中止
  • locked,对象加锁
  • waiting,线程正在等待
  • waiting to lock 等待上锁
  • Object.wait(),对象等待中
  • waiting for monitor entry 等待获取监视器(重点关注)
  • Waiting on condition,等待资源(重点关注),最多见的状况是线程在等待网络的读写

实战案例1:jstack 分析死锁问题

  • 什么是死锁?
  • 如何用jstack排查死锁?

什么是死锁?

死锁是指两个或两个以上的线程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法进行下去。

如何用如何用jstack排查死锁问题

先来看一段会产生死锁的Java程序,源码以下:

/**
 * Java 死锁demo
 */
public class DeathLockTest {
    private static Lock lock1 = new ReentrantLock();
    private static Lock lock2 = new ReentrantLock();

    public static void deathLock() {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    lock1.lock();
                    System.out.println(Thread.currentThread().getName() + " get the lock1");
                    Thread.sleep(1000);
                    lock2.lock();
                    System.out.println(Thread.currentThread().getName() + " get the lock2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                try {
                    lock2.lock();
                    System.out.println(Thread.currentThread().getName() + " get the lock2");
                    Thread.sleep(1000);
                    lock1.lock();
                    System.out.println(Thread.currentThread().getName() + " get the lock1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //设置线程名字,方便分析堆栈信息
        t1.setName("mythread-jay");
        t2.setName("mythread-tianluo");
        t1.start();
        t2.start();
    }
    public static void main(String[] args) {
        deathLock();
    }
}
复制代码

运行结果:

显然,线程jay和线程tianluo都是只执行到一半,就陷入了阻塞等待状态~

jstack排查Java死锁步骤

  • 在终端中输入jsp查看当前运行的java程序
  • 使用 jstack -l pid 查看线程堆栈信息
  • 分析堆栈信息

在终端中输入jsp查看当前运行的java程序

经过使用 jps 命令获取须要监控的进程的pid,咱们找到了 23780 DeathLockTest

使用 jstack -l pid 查看线程堆栈信息

由上图,能够清晰看到 死锁信息:

  • mythread-tianluo 等待这个锁 “0x00000000d61ae3a0”,这个锁是因为mythread-jay线程持有。
  • mythread-jay线程等待这个锁“0x00000000d61ae3d0”,这个锁是由mythread-tianluo 线程持有。

还原死锁真相

“mythread-tianluo"线程堆栈信息分析以下:

  • mythread-tianluo的线程处于等待(waiting)状态,持有“0x00000000d61ae3d0”锁,等待“0x00000000d61ae3a0”的锁

“mythread-jay"线程堆栈信息分析以下:

  • mythread-tianluo的线程处于等待(waiting)状态,持有“0x00000000d61ae3a0”锁,等待“0x00000000d61ae3d0”的锁

实战案例2:jstack 分析CPU太高问题

来个致使CPU太高的demo程序,一个死循环,哈哈~

/**
 * 有个致使CPU太高程序的demo,死循环
 */
public class JstackCase {

     private static ExecutorService executorService = Executors.newFixedThreadPool(5);

    public static void main(String[] args) {

        Task task1 = new Task();
        Task task2 = new Task();
        executorService.execute(task1);
        executorService.execute(task2);
    }

    public static Object lock = new Object();

    static class Task implements Runnable{

        public void run() {
            synchronized (lock){
                long sum = 0L;
                while (true){
                    sum += 1;
                }
            }
        }
    }
}
复制代码

jstack 分析CPU太高步骤

    1. top
    1. top -Hp pid
    1. jstack pid
    1. jstack -l [PID] >/tmp/log.txt
    1. 分析堆栈信息

1.top

在服务器上,咱们能够经过top命令查看各个进程的cpu使用状况,它默认是按cpu使用率由高到低排序的

由上图中,咱们能够找出pid为21340的java进程,它占用了最高的cpu资源,凶手就是它,哈哈!

2. top -Hp pid

经过top -Hp 21340能够查看该进程下,各个线程的cpu使用状况,以下:

能够发现pid为21350的线程,CPU资源占用最高~,嘻嘻,小本本把它记下来,接下来拿jstack给它拍片子~

3. jstack pid

经过top命令定位到cpu占用率较高的线程以后,接着使用jstack pid命令来查看当前java进程的堆栈状态,jstack 21350后,内容以下:

4. jstack -l [PID] >/tmp/log.txt

其实,前3个步骤,堆栈信息已经出来啦。可是通常在生成环境,咱们能够把这些堆栈信息打到一个文件里,再回头仔细分析哦~

5. 分析堆栈信息

咱们把占用cpu资源较高的线程pid(本例子是21350),将该pid转成16进制的值

在thread dump中,每一个线程都有一个nid,咱们找到对应的nid(5366),发现一直在跑(24行)

这个时候,能够去检查代码是否有问题啦~ 固然,也建议隔段时间再执行一次stack命令,再一份获取thread dump,毕竟两次拍片结果(jstack)对比,更准确嘛~

参考与感谢

我的公众号

  • 以为写得好的小伙伴给个点赞+关注啦,谢谢~
  • 若是有写得不正确的地方,麻烦指出,感激涕零。
  • 同时很是期待小伙伴们可以关注我公众号,后面慢慢推出更好的干货~嘻嘻
  • github地址:github.com/whx123/Java…
相关文章
相关标签/搜索