JVM系列(三十) JVM调优实战-Jstack命令实战-线程状态分析jstack日志

787 阅读7分钟

Jstack命令实战-jstack日志线程状态分析

前面我们讲过了内存过高,持久不下的场景,很有可能是内存泄漏,或者该回收的对象没有回收,或者是自己的内存参数设置的有问题,无法回收垃圾对象导致的,但是这一部分只是分析内存过高的问题

在实际的项目中,我们不仅仅会碰到内存过高,另一个经常遇见的就是 CPU过高CPU过高 也是经常出现的事情,出现的原因也是各种各样的,一旦遇到CPU过高,我们就需要有一定的技能去解决 CPU过高 的问题,这就是Jstack命令的作用

今天我们就是用Jstack命令,看下是否能够解决CPU过高的问题,定位到问题,解决问题

1.Jstack 命令详解

什么是Jstack命令,Jstack 是生成Java虚拟机当前时刻的线程快照,用于保存当前线程快照便于堆栈跟踪的,它有什么作用?

  • 定位线程长时间停顿原因
  • 定位线程死锁问题
  • 定位线程死循环问题
  • 定位线程阻塞问题

这些问题都是导致CPU过高的原因,下面我们来介绍一下Jstack的使用

C:\Users\jzj>jstack -options
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

2.Jstack实战

首先先启动Springboot程序, jps查看java进程信息 JvmtestApplication 进程PID 28596

E:\setup\jar>jps
9792 KotlinCompileDaemon
28596 JvmtestApplication
29560
10028 Launcher
14140 Jps
4908 RemoteMavenServer36

E:\setup\jar>

3.jstack [-option] pid 打印进程堆栈信息 必会

执行jstack [-option] pid 可以打印某个java进程的堆栈信息,上面我们已经知道了28596是我们的进程PID信息,jstack 有哪些参数

  • -F 当进程假死的时候,比如CPU很高的时候,jstack指令无响应的时候,强制打印堆栈信息
  • -m 打印包含Java和C/C++的混合模式堆栈跟踪
  • -l 打印锁的信息 l代表lock 锁的意思
  • -h help信息,帮助信息

下面我们一一看下 jstack 28596 打印进程堆栈信息 如下:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):

"DestroyJavaVM" #27 prio=5 os_prio=0 tid=0x00000000038d5000 nid=0x467c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"http-nio-8078-Acceptor" #26 daemon prio=5 os_prio=0 tid=0x000000001b52c800 nid=0x4f90 runnable [0x000000001cdfe000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
        at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
        at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
        - locked <0x00000000fe08a0f8> (a java.lang.Object)
        at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:540)
        at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:78)
        at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:106)
        at java.lang.Thread.run(Thread.java:748)
        
"container-0" #24 prio=5 os_prio=0 tid=0x000000001b44a000 nid=0x575c waiting on condition [0x000000001c9ff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at org.apache.catalina.core.StandardServer.await(StandardServer.java:563)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer$1.run(TomcatWebServer.java:197)

"Catalina-utility-2" #23 prio=1 os_prio=-2 tid=0x000000001b17f000 nid=0x4f74 waiting on condition [0x000000001c8fe000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000fc3be108> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

"Catalina-utility-1" #22 prio=1 os_prio=-2 tid=0x000000001b17e800 nid=0x41e0 waiting on condition [0x000000001c7ff000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000fc3be108> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)


"RMI TCP Accept-0" #14 daemon prio=5 os_prio=0 tid=0x0000000019b69000 nid=0x52d8 runnable [0x000000001a37e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.DualStackPlainSocketImpl.accept0(Native Method)
        at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
        at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
        at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
        - locked <0x00000000fa2579e8> (a java.net.SocksSocketImpl)
        at java.net.ServerSocket.implAccept(ServerSocket.java:545)
        at java.net.ServerSocket.accept(ServerSocket.java:513)
        at sun.management.jmxremote.LocalRMIServerSocketFactory$1.accept(LocalRMIServerSocketFactory.java:52)
        at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(TCPTransport.java:405)
        at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(TCPTransport.java:377)
        at java.lang.Thread.run(Thread.java:748)

"C2 CompilerThread0" #8 daemon prio=9 os_prio=2 tid=0x0000000018e75000 nid=0x5640 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #7 daemon prio=5 os_prio=0 tid=0x0000000018ebb800 nid=0x3500 runnable [0x000000001945e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000fa231298> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000fa231298> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #6 daemon prio=5 os_prio=2 tid=0x00000000180eb800 nid=0x5bd0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x00000000180c4000 nid=0x5a94 in Object.wait() [0x0000000018bee000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000fa2d7258> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x00000000fa2d7258> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000180bd800 nid=0x1f10 in Object.wait() [0x0000000018aef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000fa257c18> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000fa257c18> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x0000000018099000 nid=0x4cd4 runnable

"Gang worker#0 (Parallel GC Threads)" os_prio=2 tid=0x00000000038ed800 nid=0x5d38 runnable


"VM Periodic Task Thread" os_prio=2 tid=0x0000000019b76000 nid=0x56ec waiting on condition

JNI global references: 1334
4.Jstack 分析线程状态

我们可以看到 有下面这些 信息 ,可以看到 大概的线程状态 打印出来的有这么几种

  • java.lang.Thread.State: RUNNABLE
  • java.lang.Thread.State: WAITING (on object monitor)
  • java.lang.Thread.State: WAITING (parking)
  • java.lang.Thread.State: TIMED_WAITING (sleeping)

其实通过jstack 线程堆栈Dump文件,只会产生 3种状态

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

这些 Runnable , waiting, time waiting, sleep 等状态,其实都是线程的状态,那么线程有哪些状态呢? 1. Java语言定义了6种线程池状态:

  • New:新建状态
    • 我们newThread() 创建线程,尚未启动的线程也就是没有调用start的时候,线程处于这种状态,在我们jstack命令执行过程中,是不会出现new线程状态
  • RUNNABLE:运行状态
    • 运行状态包括Running 运行中和Ready就绪状态都成为 Runnable可运行状态
    • 线程开启start()方法,线程才会可能真正的运行起来,执行start后,线程会进入Ready状态,表示当前线程已经准备好,随时等待CPU资源启动线程
    • 就绪状态Ready是线程 转换为 运行状态的唯一入口
    • Runnable 表示线程处于运行中,是活跃线程
  • Waiting:等待状态
    • 没有时间限制,无限的等待另一个线程的特定操作
  • Timed Waiting:有时间限制的的等待
    • 有限时间内,等待另一个线程的特定操作
  • 阻塞(Blocked):阻塞状态
    • 在程序等待进入同步区域 synchronize的时候,线程将进入这种状态,在等待 监视器锁
  • 结束(Terminated):结束状态
    • 已终止线程的线程状态,线程已经结束执行。

既然线程有这么多状态,那么每种状态之间是如何切换的,或者每种状态是如何产生的?

5.线程状态转换过程

下面我们用这张图来说明线程状态的转换过程

image.png

  1. 创建线程,我们通过程序 new Thread 创建线程,这时候就进入了New状态
  2. 线程调用start() 方法,然后此线程就会进入就绪状态,就绪状态是不会立即运行的,只有CPU时间片轮转,资源分到了该线程,该线程才会启动
  3. 线程抢占到CPU时间片后,线程会进入运行状态,开始执行run()方法
  4. 线程运行过程中,因为某种原因放弃了CPU的使用权,该线程就处于阻塞状态,暂停运行
  5. 阻塞状态分为3种 等待阻塞, 同步阻塞, 其他阻塞
  6. 等待阻塞 就是线程执行了wait()方法, 该线程释放所有的资源,进入等待池,wait状态是不会主动苏醒的,必须依靠其他线程主动的调用notify或者notifyAll 唤醒方法,才能被唤醒,被唤醒后,它处于 Ready状态中,等待CPU资源,执行线程
  7. 同步阻塞 就是运行的线程遇到了对象的同步锁,比如synchronized, 如果该同步锁现在被其他对象持有,还未释放,那么JVM会把当前线程放入到 同步锁池中,监听锁释放消息,拿到锁才能变为 Ready状态
  8. 其他阻塞 就是线程遇到其他请求比如执行 sleep()方法,或者执行 join()方法 或者 发出了I/O请求,都会使当前线程处于阻塞状态, 当sleep超时,或者 join等待线程终止/超时, 或者I/O请求处理完毕, 线程就会重返Ready就绪状态
  9. 线程运行完毕,或者Interrupt中断,就会结束线程生命周期,这时候线程处于终止状态

image.png


我们讲解了Jstack的用法,在分析jstack日志的时候我们了解了线程的状态,线程分为5种状态,New新建,Runnable运行/就绪,Waiting等待,Blocking阻塞,Terminated终止等状态,了解了状态切换信息 下一篇我们介绍线程常用的方法