相信很多研发的小伙伴已经在网上get到了各种线程分析技能,我这里关于相关知识点就不展开叙述了。
背景:
项目某并行的任务进度不走卡在某一进度,服务CPU持续飙高,由于该任务是做数据预处理的(大量数据库操作),下级单位还等着用这个批次的数据,所以直接进行简短的现场排查。
1.检查jvm启动参数是否合理
经查看启动jvm启动参数没有问题,jvm调参这个我们这里先不讨论
2.检查数据库
1.判断总条数是否异常。条数较多可怀疑有问题,并多次执行判断是否有哪些sql长期不消失
oracle:
SELECT B.SID ORACLEID,
B.USERNAME 登录ORACLE用户名,
PADDR,
B.OSUSER,
B.MACHINE 计算机名,
B.SECONDS_IN_WAIT,
B.SQL_ID,
b.SQL_CHILD_NUMBER,
B.STATUS,
('alter system kill session ''' || b.sid || ',' || b.serial# || ''';') AS killsql,
(SELECT C.ELAPSED_TIME/1000000 FROM V$SQL C WHERE C.SQL_ID = B.SQL_ID and b.SQL_CHILD_NUMBER=c.CHILD_NUMBER) AS ELAPSED_TIME_累计秒,
(SELECT (case when EXECUTIONS=0 then 0 else C.ELAPSED_TIME/c.EXECUTIONS/1000000 end) FROM V$SQL C where C.SQL_ID = B.SQL_ID and b.SQL_CHILD_NUMBER=c.CHILD_NUMBER) AS 平均单次秒,
(SELECT C.SQL_TEXT FROM V$SQL C WHERE C.SQL_ID = B.SQL_ID and b.SQL_CHILD_NUMBER=c.CHILD_NUMBER) AS SQL_TEXT,
(SELECT C.SQL_FULLTEXT FROM V$SQL C WHERE C.SQL_ID = B.SQL_ID AND ROWNUM <= 1) AS SQL_FULLTEXT
FROM V$SESSION B
WHERE B.OSUSER <> 'oracle'
AND B.SQL_ID IS NOT NULL
ORDER BY 平均单次秒 desc nulls last;
mysql:
select id, db, user, host, command, time, state, info from information_schema.processlist where command != 'Sleep' order by time desc
2.检查等待会话
3.检查是否有锁表
如何查询Oracle数据库表是否被锁_Zoe_YuZu的博客-CSDN博客_oracle查看是否锁表
数据库如果发现锁表,可以通过杀掉被锁的会话临时解决问题,发生这种问题最终还是要落实到代码中解决。
解决方法: 这种死锁是由于你的程序的'bug'产生的,除了调整你的程序的逻辑别无他法,仔细分析你程序的逻辑, 1:尽量避免同时锁定两个资源 2: 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源.
3.排查java线程栈
1.找到最耗CPU的进程
2.找到最耗CPU的线程
3.查看堆栈,定位线程执行逻辑,定位代码
1.使用 top -p <pid>(或者使用top -c按大写Pcpu排序) 命令(<pid>为Java进程的id号)按照cpu使用率排序查看Java进程的cpu占用:
cpu已经被java进程占用了100.0%
2.使用 top -Hp <pid> 命令(<pid>为Java进程的id号)查看该Java进程内所有线程的资源占用情况
发现一个线程把CPU线程占满了
3.这个时候我们可以选择做一下PID(7200)dump飞行记录等或者先将7200转换春耕16进制做整体java的线程记录
做线程dump方法:
使用dk自带命令jstack获取此时的线程快照并输入到文件中: jstack -l <pid> > ./jstack_result.txt 命令(<pid>为Java进程的id号)来获取线程快照结果并输入到指定文件。
4.查看堆栈,定位线程
1.当我们发现多个线程都占用非常高的CPU的时候
使用 printf "%x\n" <tid> 命令(tid指线程的id号)将以上10进制的线程号转换为16进制:
转换后的结果分别为lc20,lc21,由于16进制以0x开头,所以对应的16进制的线程号为0xlc20和0xlc21。
然后做整个java进程的线程dump
通过16进制线程号nid查看线程dump线程快照
示例:
2.直接做7200的线程dump(因为通过观察一个线程就把CPU打满,所以直接做一个就可以)
直接锁定问题点,并通过@bci=12, line=965等信息直接锁定代码位置
接下来就是读代码时间了。。。
4.除了线程快照可以帮助我们分析问题,我们还可以借助gc情况,cpu使用情况等
确认FGC次数是否频繁
重点看O列和FGC对应列,理想的情况O<90,并且15秒内FGC增长数<=1。
到这里基本上我们这次的排查也就结束了
5.在最后附上我使用持续监控的脚本
抱大佬大腿
#!/bin/bash
while(true)
do
######################### 请按实际情况调整如下内容 #####################
#设置要监控的应用的端口
port=8080
#设置java_home所在路径
java_home=/data/jq/jdk1.8.0_181
#设置监控日志输出位置
output=/data/jq/xnjk
########################################################################
######################### 以下内容请不要随意调整 #######################
#初始化变量
pid=$(netstat -tlnp| grep $port | awk '{print $7}' |awk -F/ '{print $1}'|awk 'NR==1')
java_dir=$java_home/bin
dir_out=$output/xnjk_$port/`date +%Y%m%d`/`date +%Y%m%d_%H`/`date +%Y%m%d_%H%M%S`
mkdir -p $dir_out
#输出监控结果
ps H -eo user,pid,ppid,tid,time,%cpu,cmd --sort=-%cpu | awk 'NR<12{print}' >$dir_out/cpu_thread.log
top -p $pid -b -H -n 1 >$dir_out/cpu_top_"$pid".log
$java_dir/jstack $pid >$dir_out/threaddump_"$pid".txt
$java_dir/jstat -gcutil $pid >$dir_out/gc_"$pid".log
$java_dir/jstat -gc $pid >>$dir_out/gc_"$pid".log
#输出netstat监控
echo -e "`date +%Y%m%d_%H%M%S` \t $(netstat -anp | wc -l) \t $(netstat -anp | grep TIME_WAIT | wc -l)" >> $dir_out/netstat.log
#输出操作系统资源监控
vmstat -S M 1 15 >>$dir_out/vmstat_"$pid".log
#设置日志保留天数
num=3
#压缩最近n天日志,删除n天之前日志,n在上面设置,默认为3天
#一整天日志不压缩一般在1.5G以内,加上前几天压缩后的日志,总共占磁盘在2G以内
cd $output/xnjk_$port
for((i=1;i<=$num;i++))
do
day_ago=`date -d "$i days ago" +%Y%m%d`
if [ `ls | grep -v tar.gz | grep $day_ago |wc -l` -ne 0 ];then ls | grep -v tar.gz | grep "$day_ago" | xargs tar -czf $day_ago.tar.gz; fi
ls | grep -v tar.gz | grep "$day_ago" | xargs rm -rf
str="$str\|$day_ago"
done
ls | grep `date +%Y` | grep -v "`date +%Y%m%d`$str" | xargs rm -rf
########################################################################
done