jstack - Prints Java thread stack traces for a Java process, core file, or remote debug server.

Jstack主要的作用是生成当前进程中所有线程的信息,也就是当前时刻JVM的线程快照,通过线程的信息我们可以定位到程序中出现长时间停顿、CPU占用率过高等问题。

线程快照中的信息是当前java虚拟机内每一条线程正在执行的方法的堆栈集合,有了堆栈信息我们就可以分析出我们的程序问题出现在哪,比如线程间死锁、外部资源请求时间过长、死循环等。

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
jstack [ options ] pid

jstack [ options ] executable core

jstack [ options ] [ server-id@ ] remote-hostname-or-IP


OPTIONS
-F
Force a stack dump when jstack [-l] pid does not respond.

-l
Long listing. Prints additional information about locks such as a list of owned java.util.concurrent ownable synchronizers. See the
AbstractOwnableSynchronizer class description at
http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/AbstractOwnableSynchronizer.html

-m
Prints a mixed mode stack trace that has both Java and native C/C++ frames.
  • -F 当正常的请求不被响应时,强制输出堆栈信息。
  • -l 额外打印锁的信息,当发生死锁时可以查看锁的信息
  • -m 如果调用本地方法栈的信息,可以打印C/C++的堆栈

以一个发生死锁的例子来看一下使用Jstack查看到的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Jstack {

private static Object obj1 = new Object();
private static Object obj2 = new Object();

public static void main(String[] args) {

new Thread(() -> {
synchronized (obj1) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
}
}
}).start();
new Thread(() -> {
synchronized (obj2) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
}
}
}).start();
}
}

上面代码中,第一个线程拿到obj1的锁,等待obj2的锁,第二个线程拿到obj2的锁,等待obj1的锁,这样就会发生死锁。

先通过jps命令获取到先拿到当前的进程pid,然后通过jstack获取线程的信息。可以看到有两个线程都处于阻塞状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"Thread-1" #12 prio=5 os_prio=0 tid=0x00007fdff871c800 nid=0x3cc2 waiting for monitor entry [0x00007fdfce0fc000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.demo.jstack.Jstack.lambda$main$1(Jstack.java:36)
- waiting to lock <0x000000076e925a90> (a java.lang.Object)
- locked <0x000000076e925aa0> (a java.lang.Object)
at com.example.demo.jstack.Jstack$$Lambda$2/2052001577.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"Thread-0" #11 prio=5 os_prio=0 tid=0x00007fdff871a800 nid=0x3cc1 waiting for monitor entry [0x00007fdfce1fc000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.demo.jstack.Jstack.lambda$main$0(Jstack.java:25)
- waiting to lock <0x000000076e925aa0> (a java.lang.Object)
- locked <0x000000076e925a90> (a java.lang.Object)
at com.example.demo.jstack.Jstack$$Lambda$1/1174361318.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

第一行显示线程名、线程优先级、线程id、线程状态描述等信息

第二行显示的是当前线程的状态

​ Java中线程的状态分为NEW、RUNNABLE、BLOCKED、WATING、TIMED_WATING、TERMINATED,但是在快照中NEW状态是不会出现的。

再下面的就是当前线程的调用栈的信息。调用栈中包含了锁的信息。

locked 表示使用synchronized申请对象锁成功,监视器的拥有者

waiting to lock 表示使用synchronized申请对象锁未成功,进入等待区。

waiting on 表示用synchronized申请对象锁成功后,调用了wait方法,进入对象的等待区等待。

parking to wait for park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包会出现的新的机制,与synchronized体系不同。

在最后也显示出了代码中出现死锁的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007fdfac006638 (object 0x000000076e925a90, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007fdfac003da8 (object 0x000000076e925aa0, a java.lang.Object),
which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.example.demo.jstack.Jstack.lambda$main$1(Jstack.java:36)
- waiting to lock <0x000000076e925a90> (a java.lang.Object)
- locked <0x000000076e925aa0> (a java.lang.Object)
at com.example.demo.jstack.Jstack$$Lambda$2/2052001577.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.example.demo.jstack.Jstack.lambda$main$0(Jstack.java:25)
- waiting to lock <0x000000076e925aa0> (a java.lang.Object)
- locked <0x000000076e925a90> (a java.lang.Object)
at com.example.demo.jstack.Jstack$$Lambda$1/1174361318.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

好了,熟悉了Jstack,我们用一段死循环的代码,通过Jstack来定位到使CPU占用100%的代码行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class JstackDemo {
public static Executor executor = Executors.newFixedThreadPool(3);
private static Object lock = new Object();

public static void main(String[] args) {
Task task1 = new Task();
Task task2 = new Task();
executor.execute(task1);
executor.execute(task2);
}

public static class Task implements Runnable {

@Override
public void run() {
synchronized (lock) {
run0();
}
}

private void run0() {
int i = 0;
while (true) {
i++;
}
}
}
}

1、首先通过top查看到使CPU占用到100%的进程id

2、使用top -Hp 进程id 查看占用CPU最多的线程id

3、将线程id转换为16进制

17997 -> 464d

4、使用Jstack查看Java所在的进程,并找到相应的线程