一、jstack介绍:

  • jstack是jdk自带的线程堆栈分析工具,使用该命令可以查看或导出 java 应用程序中线程堆栈信息。
  • jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。

jstack指令: 

java线程号找对应的堆栈 jdk线程堆栈分析工具_死锁

说明:

使用方式

说明

jstack [ option ] pid

查看当前时间点,指定进程的dump堆栈信息。

jstack [ option ] pid > 文件

将当前时间点的指定进程的dump堆栈信息,写入到指定文件中。

注:若该文件不存在,则会自动生成;若该文件存在,则会覆盖源文件。

jstack [ option ] executable core

查看当前时间点,core文件的dump堆栈信息。

jstack [ option ] [server_id@]<remote server IP or hostname>

查看当前时间点,远程机器的dump堆栈信息。

可选参数说明

-F

当进程挂起了,此时'jstack [-l] pid'是没有相应的,这时候可使用此参数来强制打印堆栈信息,强制jstack),一般情况不需要使用

-m

打印java和native c/c++框架的所有栈信息。可以打印JVM的堆栈,以及Native的栈帧,一般应用排查不需要使用

-m

长列表. 打印关于锁的附加信息。例如属于java.util.concurrent的ownable synchronizers列表,会使得JVM停顿得长久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。一般情况不需要使用

-h or -help

打印帮助信息。


二、简单使用示例:

jps查看java进程:

java线程号找对应的堆栈 jdk线程堆栈分析工具_java_02

jstack查看指定进程的当前堆栈情况:

java线程号找对应的堆栈 jdk线程堆栈分析工具_java线程号找对应的堆栈_03

将指定进程的当前堆栈情况记录到某个文件中: 

java线程号找对应的堆栈 jdk线程堆栈分析工具_死锁_04


jstack实战之高cpu占用率排查: 

在几乎没有什么调用的情况下,服务器cpu使用率一直居高不下,非常有可能时程序中的某个线程出现了死循环。

提示:在业务量不大的情况下,cpu占用率过高,死循环以外,常见的可能还有【内存泄漏,导致大量full GC】

准备工作:运行下述代码,死循环,造成CPU占用较高的情况。

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@SuppressWarnings("all")
public class JstackTest {
    
    private static Executor executor = Executors.newFixedThreadPool(5);
    
    private static final Object lock = new Object();

    public static void main(String[] args) {
        MyRunnableImpl mri1 = new MyRunnableImpl();
        MyRunnableImpl mri2 = new MyRunnableImpl();
        executor.execute(mri1);
        executor.execute(mri2);
    }
    
    static class MyRunnableImpl implements Runnable{

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

排查开始:

第一步:使用top指令,定位CPU占用较高的进程。

java线程号找对应的堆栈 jdk线程堆栈分析工具_死锁_05

第二步:使用top -H -p ${进程id}指令,查看指定进程下各个线程的cpu使用情况。

java线程号找对应的堆栈 jdk线程堆栈分析工具_java_06

注:PID虽然名为进程控制符,但其用途不限于特指进程id。如上图中PID列指的就是线程的id。

注:上图中,线程id有一个为1956的,与进程id一样。本人怀疑main线程的id与进程id一致

 第三步:使用printf "%x" xxx指令,将1977转换为16进制。

java线程号找对应的堆栈 jdk线程堆栈分析工具_java线程号找对应的堆栈_07

 第四步:使用jstack pid指令,查看当前的堆栈信息;并根据上一步得到的16进制的线程id,找到肇事线程。

java线程号找对应的堆栈 jdk线程堆栈分析工具_堆栈_08

 提示:如果线程较多的话,我们可以使用grep指定来根据关键字定位行,如:

jstack 1956 | grep -10 7b9:显示【|】前面的指令打印出的信息中的,含有关键字【7b9】的那一行以及其前后各10行的数据

java线程号找对应的堆栈 jdk线程堆栈分析工具_死锁_09

 注:jstack 1956 | grep 7b9:显示【|】前面的指令打印出的信息中的,含有关键字【7b9】的那一行数据。

注:jstack 1956 | grep -A10 7b9:显示【|】前面的指令打印出的信息中的,含有关键字【7b9】的那一行以及其后10行的数据。

注:jstack 1956 | grep -B10 7b9:显示【|】前面的指令打印出的信息中的,含有关键字【7b9】的那一行以及其前10行的数据。

第五步:分析肇事线程堆栈信息。

java线程号找对应的堆栈 jdk线程堆栈分析工具_堆栈_10

  • ①线程名。
  • ②线程优先级。
  • ③一个地址(该地址是什么地址,存疑)。
  • ④线程十六进制id。
  • ⑤线程状态。
  • ⑥线程当前所处方法。
  • ⑦该箭头表示线程的执行历程。

 注:此时,我们已经定位到了问题代码的位置⑥,接下来查看程序相应位置的代码,解决死循环问题即可。


jstack实战之死锁线程的定位:

准备工作:运行下述代码,造成死锁发生的情况。

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@SuppressWarnings("all")
public class JstackTest {

    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 mri1 = new MyRunnableImplOne();
        MyRunnableImplTwo mri2 = new MyRunnableImplTwo();
        executor.execute(mri1);
        executor.execute(mri2);
    }

    static class MyRunnableImplOne implements Runnable{
        @Override
        public void run() {
            synchronized (lockA){
                System.out.println(Thread.currentThread().getName() + "获得锁A");
                //循环一会儿,给另一个线程一点时间,获取到另一把锁
                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() + "获得锁B");

                //循环一会儿,给另一个线程一点时间,获取到另一把锁
                int i = 10;
                while (i > 10) {
                    i--;
                }
                synchronized (lockA){
                    System.out.println(Thread.currentThread().getName() + "获得锁A");
                }
            }
        }
    }
}

java线程号找对应的堆栈 jdk线程堆栈分析工具_死锁_11

排查开始:

第一步:使用jps指令,查看所有java进程。

 

java线程号找对应的堆栈 jdk线程堆栈分析工具_堆栈_12

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

java线程号找对应的堆栈 jdk线程堆栈分析工具_java_13

 注:到这里为止,发生死锁的线程就定位到了。

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