阅读 1026

频繁GC导致CPU飙高实战

一、问题场景

测试环境突然报警CPU飙高,查看日志发现不停的在做GC操作,8个GC线程把CPU打爆。

二、问题排查

首先就是保留现场,打印堆栈信息。

1.打印线程运行信息

jstack 85090 > code-api.log
复制代码

2.打印堆信息

jmap -dump:format=b,file=heapdump1.hprof 85090
复制代码

3.分析dump文件

image.png 图中可以看到,StackTraceElement的数量非常惊人。

StackTraceElement每个元素表示单独的一个堆栈帧。所有的堆栈帧(堆栈顶部的那个堆栈帧除外)都表示一个方法调用。

因此可以确定是某个方法做了无限递归调用,不停的开启新的栈帧

4.分析线程运行日志

image.png 发现所有的工作线程不停的调用AlarmService中的这个方法,那问题基本就定位到了。

5.查看AlarmService

发现方法是调用了一个RPC请求,原来是测试环境服务器迁移,导致没法调通原有ip,将ip改为域名后即可。

三、问题分析

1.查看Feign配置

RPC调用使用了Feign,经排查配置了NEVER_RETRY(从不重试)参数,为什么还会不停的重新请求呢。

2.AOP

查看了告警机制的实现,是通过AOP切面来捕获异常进行处理。
也就是说告警Service连接告警中心服务器,5秒后超时报了超时异常,这个超时异常也被AOP捕获了,之后告警Service要将超时异常报给告警中心,但告警中心无法连接还是会报超时异常,就导致了不停递归打开新方法,问题到这里就完全搞清了。

四、解决方法

这里发现了潜在的坑,就是告警中心服务器如果不稳定,势必会影响线上服务的正常运行,这是不可接受的,因此要想办法避免这种情况的再次发生。

1.try-catch

  • 对alermService中的方法添加try-catch,发现居然并不生效,并不能catch到异常。
  • 后来分析是Service下层调用RPC接口时产生的异常,被catch之前就被aop先捕获到了。
  • 把try-catch加到rpc层,问题解决。

不过这种解决方式不够优雅,不能把每rpc调用都try-catch处理。

2.aop

通过aop配置来解决。
原有为

@Pointcut(execution(* com.qbq.test..*.*(..)))
复制代码

修改为

@Pointcut(execution(* com.qbq.test..*.*(..)) && !execution(* com.qbq.test.alarm..*.*(..)))
复制代码

即aop的切面不再且alarm包,不会再捕获alarm包中的异常了。

五、一些疑问

1.无限递归调用为什么没有导致StackOverFlowError

本地验证后分析得出结论: 是因为请求超时时间设置的是5秒。
如果将超时事件改为5ms,很快就会报StackOverFlow,栈被打爆了。
如果是5秒的话,因为方法调用没结束,栈是不会弹出的。很多个异常同时上报的话,StackTraceElement很快把新生代占满,导致JVM不停的做YOUNG GC操作。

2.确定CPU飙高是由GC导致的吗

image.png 因为当时日志一直在刷GC信息,理所当然觉得是由GC导致的。
后本地模拟了一下,通过arthas的thread命令查看线程工作情况。可以看到GCThread线程有8个,CPU占用率都很高。 至此确定了CPU飙高是由于GC导致的。

文章分类
后端
文章标签