JVM 性能调优 jstack

 2020-07-01 

JVM 性能调优 jstack

Jstack是Jdk自带的线程跟踪工具,用于打印指定Java进程的线程堆栈信息

命令

jstack pid > dump文件名

1
jstack 13522 > dump01

查看pid命令

1
jps

查看进程下哪些线程占用了高的cpu

1
top -p pid -H

线程状态

状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时等待状态,该状态不同于WAITING,它是可以在指定时间内自行返回的
TERMINATER 终止状态,表示当前线程已经执行完毕

jstack参数解析

  • tid: java内的线程id
  • nid: 操作系统级别线程的线程id
  • prio: java内定义的线程的优先级
  • os_prio:操作系统级别的优先级
  • Elapsed Time = Cpu Time + Wait Time

CPU Time 指的是CPU在忙于执行当前任务的时间,其并没有考虑等待时间,如IO等待,网络等待等,而Elapsed Time 则是执行当前任务所花费的总时间,也就是说这两者之

问的关系统可以表示为:Elapsed Time = Cpu Time + Wait Time 但是在多核处理器的情况下,由于多个CPU同时处理任务所以可能会出现Cpu Time 大于Elapsed Time 的情况

  • CPU Time:对于单线程程序来说,CPU TIME指的是该线程在一个逻辑处理器(单核)上所花费的时间总量;对于多线程程序来说,CPU TIME指的是所有线程的CPU TIME之和;应用程序的CPU时间指的是该程序所有线程的CPU TIME之和。
  • Wait Time:特定线程等待一定事件发生的时间,这些事件可以是同步等待,I/O等待。
  • Elapsed time:该程序运行的平台时间,即:应用程序结束的时刻-应用程序起始时刻。

调用修饰

表示线程在方法调用时,额外的重要的操作。线程Dump分析的重要信息。修饰上方的方法调用。

  • locked <地址> 目标:使用synchronized申请对象锁成功,监视器的拥有者。

  • waiting to lock <地址> 目标:使用synchronized申请对象锁未成功,在迚入区等待。

  • waiting on <地址> 目标:使用synchronized申请对象锁成功后,释放锁幵在等待区等待。

  • parking to wait for <地址> 目标 :需与堆栈中的"parking to wait for (atjava.util.concurrent.SynchronousQueue$TransferStack)"结合来看。first–>此线程是在等待某个条件的发生,来把自己唤醒,second–>SynchronousQueue不是一个队列,其是线程之间移交信息的机制,当我们把一个元素放入到 SynchronousQueue 中时必须有另一个线程正在等待接受移交的任务,因此这就是本线程在等待的条件。

下面我们使用jstack工具(可以选择打开终端,键入jstack或者到JDK安装目录的bin目录下执行命令),尝试查看示例代码运行时的线程信息,更加深入地理解线程状态,示例如代码清单4-3所示。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.hwj.concurrent01;

import java.util.concurrent.TimeUnit;

/**
 *
 */
public class ThreadState {
    public static void main (String[] args) {
        new Thread (new TimeWaiting (), "TimeWaitingThread").start ();
        new Thread (new Waiting (), "WaitingThread").start ();
        // 使用两个Blocked线程,一个获取锁成功,另一个被阻塞
        new Thread (new Blocked (), "BlockedThread-1").start ();
        new Thread (new Blocked (), "BlockedThread-2").start ();
    }

    // 该线程不断地进行睡眠
    static class TimeWaiting implements Runnable {
        @Override
        public void run () {
            while (true) {
                SleepUtils.second (100);
            }
        }
    }

    // 该线程在Waiting.class实例上等待
    static class Waiting implements Runnable {

        @Override
        public void run () {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait ();
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                }
            }
        }
    }

    // 该线程在Blocked.class实例上加锁后,不会释放该锁
    static class Blocked implements Runnable {
        public void run () {
            synchronized (Blocked.class) {
                while (true) {
                    SleepUtils.second (100);
                }
            }
        }
    }


    static class SleepUtils {
        public static final void second (long seconds) {
            try {
                TimeUnit.SECONDS.sleep (seconds);
            } catch (InterruptedException e) {
            }
        }
    }
}

运行该示例,打开终端或者命令提示符,键入“jps”,输出如下。

img

可以看到运行示例对应的进程ID是33672,接着再键入“jstack 33672”(这里的进程ID需要和读者自己键入jps得出的ID一致),部分输出如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"TimeWaitingThread" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=12.13s tid=0x000002167f934000 nid=0x7f2c waiting on condition  [0x000000e5f17fe00
0]
   java.lang.Thread.State: TIMED_WAITING (sleeping)//线程处于超时等待
        at java.lang.Thread.sleep(java.base@13.0.3/Native Method)
        at java.lang.Thread.sleep(java.base@13.0.3/Thread.java:335)
        at java.util.concurrent.TimeUnit.sleep(java.base@13.0.3/TimeUnit.java:446)

"WaitingThread" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=12.11s tid=0x000002167f958000 nid=0x41f4 in Object.wait()  [0x000000e5f18ff000]
   java.lang.Thread.State: WAITING (on object monitor)//线程在Waiting实例上等待
        at java.lang.Object.wait(java.base@13.0.3/Native Method)
        - waiting on <0x0000000089e9fd28> (a java.lang.Class for com.hwj.concurrent01.ThreadState$Waiting)

"BlockedThread-1" #16 prio=5 os_prio=0 cpu=0.00ms elapsed=12.09s tid=0x000002167f959000 nid=0x7fb8 waiting on condition  [0x000000e5f19ff000]
//线程获取到了Blocked.class的锁
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(java.base@13.0.3/Native Method)
        at java.lang.Thread.sleep(java.base@13.0.3/Thread.java:335)
        at java.util.concurrent.TimeUnit.sleep(java.base@13.0.3/TimeUnit.java:446)

"BlockedThread-2" #17 prio=5 os_prio=0 cpu=0.00ms elapsed=12.09s tid=0x000002167f95a000 nid=0x72b4 waiting for monitor entry  [0x000000e5f1af
e000]
   java.lang.Thread.State: BLOCKED (on object monitor)//线程阻塞在获Blocked.class示例的锁上
        at com.hwj.concurrent01.ThreadState$Blocked.run(ThreadState.java:49)
        - waiting to lock <0x0000000089ea10a0> (a java.lang.Class for com.hwj.concurrent01.ThreadState$Blocked)

通过示例,我们了解到Java程序运行中线程状态的具体含义。线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换,Java线程状态变迁如图4-1示。

img

由图4-1中可以看到,线程创建之后,调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法之后将会进入到终止状态。