提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
作为一名Java应用程序开发人员,在项目过程中难免会遇到Java进程CPU冲高的问题。今天将自己经历的一些排查方法和大家分享下。
1、首先确定服务器上哪个进程的CPU冲高了。
命令:
ps -eo pid,ppid,%mem,%cpu,cmd --sort=-%cpu|head -n 10
上面命令可以得到进程ID,即PID
2、查看冲高的进程中每个线程的CPU使用情况,取前十个线程
命令:
ps -mp pid -o THREAD,tid,time | sort -k2r | head -n 10
3、根据步骤2中的CPU比较高的线程,得到tid线程号
4、将TID线程号进行十六进制转化
命令:
printf "%x\n" tid
得到十六进制后的tid
5、查看该线程tid的详细堆栈信息
命令:
jstack PID |grep TID -A 30
注意:这里的PID是进程号,TID是十六进制转化后的TID
6、对于堆栈信息内容,以下简单说明下:
a、首先先认识下线程的几个状态的概述:
在 java.lang.Thread.State 中定义了线程的状态:
NEW
至今尚未启动的线程的状态。线程刚被创建,但尚未启动。
RUNNABLE
可运行线程的线程状态。线程正在JVM中执行,有可能在等待操作系统中的其他资源,比如处理器。
BLOCKED
受阻塞并且正在等待监视器的某一线程的线程状态。处于受阻塞状态的某一线程正在等待监视器锁,以便进入一个同步的块/方法,或者在调用 Object.wait 之后再次进入同步的块/方法。
在Thread Dump日志中通常显示为 java.lang.Thread.State: BLOCKED (on object monitor) 。
WAITING
某一等待线程的线程状态。线程正在无期限地等待另一个线程来执行某一个特定的操作,线程因为调用下面的方法之一而处于等待状态:
不带超时的 Object.wait 方法,日志中显示为 java.lang.Thread.State: WAITING (on object monitor)
不带超时的 Thread.join 方法
LockSupport.park 方法,日志中显示为 java.lang.Thread.State: WAITING (parking)
TIMED_WAITING
指定了等待时间的某一等待线程的线程状态。线程正在等待另一个线程来执行某一个特定的操作,并设定了指定等待的时间,线程因为调用下面的方法之一而处于定时等待状态:
Thread.sleep 方法
指定超时值的 Object.wait 方法
指定超时值的 Thread.join 方法
LockSupport.parkNanos
LockSupport.parkUntil
TERMINATED
线程处于终止状态。根据Java Doc中的说明,在给定的时间上,一个只能处于上述的一种状态之中,并且这些状态都是JVM的状态,跟操作系统中的线程状态无关。
b、几个比较常见的判断思路:
如果是BLOCKED状态,需要仔细分析下。
是不是因为资源被抢占,相互等待各自的资源导致死锁了。
还有一个情况是,A线程占用了锁,其他的线程也需要占用这个锁,处于等待获取锁。
对于RUNNABLE状态
可以按照步骤5多执行几次,如果都是RUNNABLE,则需要注意下是不是代码中有死循环逻辑,比如while(true)等。
如果是WAITING状态
看下是不是在等待某个对象,比如从LinkedBlockingQueue接收消息,如果LinkedBlockingQueue一直没有消息,该线程的状态将不会改变,属于正常现象。
还有一种分析思路是查看整个Thread Dump的内容,通过分析多个线程之间的关系来定位问题。
Thread Dump日志的线程信息:
“resin-22129” 线程名称:如果使用 java.lang.Thread 类生成一个线程的时候,线程名称为 Thread-(数字) 的形式,这里是resin生成的线程;
daemon 线程类型:线程分为守护线程 (daemon) 和非守护线程 (non-daemon) 两种,通常都是守护线程;
prio=10 线程优先级:默认为5,数字越大优先级越高;
tid=0x00007fbe5c34e000 JVM线程的id:JVM内部线程的唯一标识,通过 java.lang.Thread.getId()获取,通常用自增的方式实现;
nid=0x4cb1 系统线程id:对应的系统线程id(Native Thread ID),可以通过 top 命令进行查看,现场id是十六进制的形式;
waiting on condition 系统线程状态:这里是系统的线程状态,具体的含义见下面 系统线程状态 部分;
[0x00007fbe4ff7c000] 起始栈地址:线程堆栈调用的其实内存地址;
java.lang.Thread.State: WAITING (parking) JVM线程状态:这里标明了线程在代码级别的状态,详细的内容见下面的 JVM线程运行状态 部分。
线程调用栈信息:下面就是当前线程调用的详细栈信息,用于代码的分析。堆栈信息应该从下向上解读,因为程序调用的顺序是从下向上的。
这里说明一下:一般如果是查看整个Thread Dump的文件的,建议多生成几个文件,中间间隔差不多3s左右,这样可以动态的查看线程的状态变化。
命令:
jstack PID > stack.out
上述命令生成的文件,如果直接用文本方式打开的话,可读性比较差,因此推荐大家使用Thread Dump Analyzer (Java线程分析工具),可以比较直观的查看。
CPU占用率很高,响应很慢
可以参照上面的几个排查思路。
CPU占用率不高,但响应很慢
通过多个Thread Dump文件进行对比,取得 BLOCKED 状态的线程列表,通常是因为线程停在了I/O、数据库连接或网络连接的地方。
总结
Java进程CPU冲高的原因有很多很多种,很难通过固定的统一一套方法来完全定位和解决问题,通过都是结合Thread Dump文件和实际代码进行分析,所以有时间经验就也很重要,大家可以在实际项目中慢慢积累这种分析思路和应用场景。