一次现网定位:java cpu占用率过高
作者:互联网
问题描述
spring boot开发的应用部署到环境上后,没有任何业务访问,CPU利用率长期100%。
定位思路
初步判断
大量异步任务导致CPU占用率搞
排查
排查代码中使用ExcutorService的地方,及异步任务(spring的@Scheduled)。
发现有使用@Scheduled,根据配置和实际的业务处理、异步任务打印的日志,理论上不会出现cpu100%,故排除掉。
深入定位
找到CPU占用率高的线程
- 找到java进程,16
- 找到cpu占用率高的线程,top -H -p 16
- 找到线程的堆栈,转换线程号printf %x 38,查询堆栈jstack 16| grep nid=0x26 -A 10
排查结果
从堆栈信息中看不到问题代码,但是可以知道是异步任务导致的问题。
线程池是pool-1-thread-xx的,一般都是用户自己创建的,因为开源组件一般都不会使用默认的线程池名。
下一步思路
多次快速的查询堆栈,看能否捕捉到问题线程,应用层(我们自己的代码)的堆栈信息。 --失败
可见不是应用层处理量大的问题。
可疑点:ThreadPoolExecutor获取任务时没有阻塞
确认可疑点
将断点打到jdk的ThreadPoolExecutor#getTask方法,发现走的分支是圈红的代码。
keepAliveTime为默认值0,所以每次获取任务时没有阻塞,到这里问题根因已经找到了。
队列里面存的是定时任务,重复执行的;因为核心线程数设置的为0,所以走的是圈红的分支,导致不断的轮训任务而不会阻塞
问题根因追踪:为什么spring定时任务使用的线程池核心线程数为0
使用@schedule注解的bean会被spring的ScheduledAnnotationBeanPostProcessor处理。
该后置处理会分析bean中@schedule注解的方法,并转换成ScheduledTask进行调度处理。
ScheduledAnnotationBeanPostProcessorhi监控spring容器完成refresh的事件,并创建调度器开始调度任务
代码有删减
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
this.finishRegistration();
}
}
private void finishRegistration() {
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
# 设置任务调度器
this.registrar.setTaskScheduler((TaskScheduler)this.resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
this.registrar.setTaskScheduler((TaskScheduler)this.resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
# 设置线程池
this.registrar.setScheduler(this.resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
# 最终在这里完成了线程池的设置,从beanfactory里面获取ScheduledExecutorService类型的bean
this.registrar.setScheduler(this.resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
this.registrar.afterPropertiesSet();
}
我们设置ScheduledExecutorService的代码,因为max字段是protected,spring无法访问,所以该字段没有被设置,默认值为0.
到这里问题已得到验证及解决。
ScheduledExecutorService核心线程为0带来的影响
标签:现网,java,spring,任务,线程,ScheduledExecutorService,registrar,占用率 来源: https://blog.csdn.net/shuxiaohua/article/details/118515970