CPU问题排查

151 阅读5分钟

一、监控种类


1:综合治理平台-容器监控

能方便快速查看cpu变化趋势,但是每一个点都是前4分钟的一个平均值。因此只有平稳的时候的值是有效的。当大幅改变的时候每个点都是不准确的。

2:登上容器通过top pid命令查看

能精准查看当前cpu使用量,但是需要登录容器。

3:登录容器使用top -Hp pid命令

查看指定进程的线程CPU使用量。其线程pid的十六进制就是jstack命获取到的当前线程的线程id

4:通过综合治理平台获取火焰图

能获取指定时间内,程序中各个方法所使用的时间片。非常有利于在能事后复现场景后分析问题。

5:访问指定容器的/actuator/galaxy-jstack接口

该接口是奖访问时和1秒的Jstack线程栈对比,分析出查找出一模一样的线程。由于cpu速度极快,当线程处于运行状态,且不是方法级阻塞等待的情况下。发送线程栈一样的情况下极低。所以如果发现是一样的,就需要重点观察是否是死循环代码。但是难以分析由FullGC导致的cpu飙升

二、保留现场s

1:jstack

2:jmap -histo >xxx.txt【GC频繁也会引起cpu问题】

3:jmap -dump:live,format=b,file=/root/logs/dump.dump 1【docker下,需要存放到挂载到宿主机的目录下,防止被他人重启】

4:arthas profiler

三、排查路径

1.当发现系统缓慢或打开异常时,紧急查看监控平台的CPU状态。

2.当确定是CPU问题后,紧急安排人员跟进最近上线的活动,并联系运营尝试下线

3.技术负责人并发通过/actuator/galaxy-jstack接口查看线程栈情况。若能打开显示出有效数据,即可定位问题代码。(当页面迟迟打不开,说明很可能是GC引起的问题,需要关注GC数据)

4.若上述操作失败,登上容器,先下载线程栈文件,并查看对应进程的线程的cpu使用量,找到最大的线程,将线程id转成十六进制后在线程栈中找到对应的活动并下架。

5.若步骤3无法打开或者步骤4找到的是gc线程,就需要通过判断是否是gc问题。(这里注意活动代码不要捕获throwable级别的抛出,否则框架无法捕获内存溢出的错误。)

四:原因定位

一:linux线程级别分析

1:先执行top -H p pid分析耗cpu线程

如果耗cpu的TOP3线程比较比较随机,那就是一些操作引起,短期内先增加容器再优化。

如果是几个线程耗cpu,那就是一些地方有逻辑问题,比如死循环。

2:如果是几个线程,将linux线程转成10进制。【比如106耗cpu,就转成16进制-6a】

二:通过linux进程定位到java线程

1:多次执行jstack pid >> /1.txt 打印线程栈,搜索16进制进程id【比如0x6a】分析当前执行方法上下文。

2:分析runbale线程,如果大量线程都是通一段代码就是那段代码问题

三:通过工具分析

1:用arthas的profiler,查看线程火焰图

2:通过步骤2或者GC命令查看是否是GC问题

3: jstack PID>> .../dump名称.tdump, 生成dtump文件,通过jca分析(目前还未使用过)

tips:

VM thread线程如果耗CPU高,说明是GC引起

处理方式:

1:先禁用问题机器,解决直接问题。

2:保留问题机器现场。

三、场景模拟


1、死循环导致cpu飙升场景

1:在活动平台测试环境多场景准备人工制造CPU飙升的场景,并发布

//开关 private static volatile AtomicLong i = new AtomicLong(1); //触发死循环 @CustomRequestAction(id="coopCpu") public String coopCpu(UserDataApi userDataApi){ i.set(1L); while (i.get()>0L){ } return "test"; } //停止死循环 @CustomRequestAction(id="coopsTop") public String coopsTop(UserDataApi userDataApi){ i.set(0L); return "success"; }

2:访问/coopCpu.do接口,观察cpu飙升。通过/actuator/galaxy-jstack接口查看线程栈。得到如下线程栈

3:根据活动代码路径快速确认所属活动并下架活动,同时重启容器。


2、FullGC导致CPU飙升场景

1:在活动平台测试环境多场景准备人工制造CPU飙升的场景,并发布

@CustomRequestAction(id="coopGC") public String coopGC(UserDataApi userDataApi){ i.set(1L); List<Byte[]> contains = new LinkedList<>(); while (i.get()>0L){ contains.add(new Byte[4096*1024]); } return "success"; }

2:访问/coopGC.do接口,查看现象。发现系统迅速恢复正常,且日志展示了内存溢出以及对应活动代码

3:该排查场景只针对问题代码不停产生新对象的场景。若问题代码持有大对象不释放,且其他线程正常产生对象造成的内存溢出不适合该排查思路。


3、FullGC后,在获取dump后如何快速定位问题代码。

1:通过手动或者被动的方式生成dump文件。这里建议开启jvm参数:

-Xloggc:/dev/shm/gc.log -XX:+PrintGCDetails XX:+PrintGCApplicationStoppedTime

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/root/logs/java_heapdump.hprof

2:通过MAT打开dump文件,通过查看内存泄漏文档详情,得到如下图

即可定位到具体出问题的代码块。

3:同时也可以通过jvm自带的jvisualvm查看

4:同时也可以通过分析内存对象,找到泄漏的对象,通过引用找到对应线程分析

分析发现就是该线程持有,因此可判断是此线程导致的问题。

五、注意点

活动代码不要捕获throwable级别的抛出,否则框架无法捕获内存溢出的错误。

上传dump文件时注意,由于容器随时会重启,要么保存dump文件到挂载在宿主机目录下的路径。要么立马上传到oss。

gitcode.net/wzpyjxz1231…

tips:

快速查看内存对象分布:jmap -histo:live pid

dump内存文件: jmap -dump:format=b,file=dump.hprof pid【在fullgc下无效】