Java进程CPU冲高问题排查

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

作为一名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文件和实际代码进行分析,所以有时间经验就也很重要,大家可以在实际项目中慢慢积累这种分析思路和应用场景。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值