一、监控种类
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。
tips:
快速查看内存对象分布:jmap -histo:live pid
dump内存文件: jmap -dump:format=b,file=dump.hprof pid【在fullgc下无效】