【SpringBoot】@Async OOM的坑及源码解析

一、背景

问题: 在前公司遇到OOM:unable to create new native thread的相关问题(1.5.6.Release的古董版本),经排查是@Async 造成

现象: 控制台日志可以发现线程名称 SimpleAsyncTaskExecutor-999线程ID一直递增 , 所以怀疑是 没有使用线程池 或者 线程池参数设置不合理,后续排查发现是@Async造成的,对此进行了排查并在团队内部做了故障排查的分享。

这两天前同事联系要帮忙解决问题,说在新公司接手老项目遇到这问题,只隐约记得之前有分享过,内容不记得了,要帮忙分析一下,写一篇分析记录下

二、使用的正确姿势

【SpringBoot】使用@Async常见问题

三、源码分析

主要是 OOM不能创建线程 问题引起,所以源码以排查OOM为主要目的,重点关注 线程 或者 线程池的创建和使用。

入门思路:既然@EnableAsync注解开启异步,那点进去看看@EnableAsync里面到底处理了啥,然后在一步一步跟就行了。

1、@EnableAsync

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 此处 引入了 AsyncConfigurationSelector.class
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
   
    ......

	/**
	 * Indicate how async advice should be applied.
	 * <p><b>The default is {@link AdviceMode#PROXY}.</b>
	 * Please note that proxy mode allows for interception of calls through the proxy
	 * only. Local calls within the same class cannot get intercepted that way; an
	 * {@link Async} annotation on such a method within a local call will be ignored
	 * since Spring's interceptor does not even kick in for such a runtime scenario.
	 * For a more advanced mode of interception, consider switching this to
	 * {@link AdviceMode#ASPECTJ}.
	 */
    AdviceMode mode() default AdviceMode.PROXY;
    
    ......
}

如上EnableAsync里面就一堆属性,大概看下都有些啥有个大体印象,后面用到的时候回来查。
稍微注意一个AdviceMode,下一步就会用到,特别注意下其注释,翻译如下:

指示应如何应用异步建议。
默认值为AdviceMode.PROXY 。
请注意,代理模式只允许通过代理拦截呼叫。
同一类中的本地调用不能以这种方式被拦截;
本地调用中此类方法上的Async注释将被忽略,因为 Spring 的拦截器甚至不会在此类运行时场景中启动。
对于更高级的拦截模式,请考虑将其切换为AdviceMode.ASPECTJ 。

@Import(AsyncConfigurationSelector.class)@Import啥意思 先不用深究,暂时知道springboot启动时会加载@Import指定的类就可以了。

点击去看下AsyncConfigurationSelector.class 写了啥

2、AsyncConfigurationSelector.class

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
   
    ......

	@Override
	@Nullable
	public String[] selectImports(AdviceMode adviceMode) {
   
		switch (adviceMode) {
   
            // 默认 AdviceMode mode() default AdviceMode.PROXY;     
			case PROXY:
				return new String[] {
   ProxyAsyncConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {
   ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
			default:
				return null;
		}
	}

}

逻辑很简单,按类型选择导入配置,因为@EnableAsync中默认 AdviceMode.PROXY,直接点进ProxyAsyncConfiguration看怎么处理

3、ProxyAsyncConfiguration

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
   

	@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
   
		Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
		// 后置处理器
        AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
		bpp.configure(this.executor, this.exceptionHandler);
		Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
		if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
   
			bpp.setAsyncAnnotationType(customAsyncAnnotation);
		}
		bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
		bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
		return bpp;
	}

}

创建了一个AsyncAnnotationBeanPostProcessor后置处理器对象,后续代码都是对此处理器的属性设置。
继承了 AbstractAsyncConfiguration ,一般Configuration都是比较重要了,点击去看一眼

@Configuration
public abstract class AbstractAsyncConfiguration implements ImportAware {
   

	@Nullable
	protected AnnotationAttributes enableAsync;

	@Nullable
	protected Executor executor;

	@Nullable
	protected AsyncUncaughtExceptionHandler exceptionHandler;


	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {
   
		this.enableAsync = AnnotationAttributes.fromMap(
				importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false));
		if (this.enableAsync == null) {
   
			throw new IllegalArgumentException(
					"@EnableAsync is not present on importing class " + importMetadata.getClassName());
		}
	}

	/**
	 * Collect any {@link AsyncConfigurer} beans through autowiring.
	 */
    // 此处加载了 AsyncConfigurer 配置
	@Autowired(required = false)
	void setConfigurers(Collection<AsyncConfigurer> configurers) {
   
		if (CollectionUtils.isEmpty(configurers)) {
   
			return;
		}
		if (configurers.size() > 1) {
   
			throw new IllegalStateException("Only one AsyncConfigurer may exist");
		}
        // 调用了上述正确使用中 方案二, 实现AsyncConfigurer接口的配置
		AsyncConfigurer configurer = configurers.iterator().next();
		this.executor = configurer.getAsyncExecutor();
		this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler();
	}

}

可以看到,setConfigurers 方法中加载了AsyncConfigurer配置,二、使用的正确姿势中提到了配置线程池方案二,就是在此时被调用加载的,如果配置了,此时 executor 已经加载了。

继续看,直接看AsyncAnnotationBeanPostProcessor怎么处理的

4、AsyncAnnotationBeanPostProcessor

public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
   


	@Nullable
	private Supplier<Executor> executor;
    

	public AsyncAnnotationBeanPostProcessor() {
   
		setBeforeExistingAdvisors(true);
	}
    
    ...... 忽略其他 ......
        
	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
   
		super.setBeanFactory(beanFactory);
        
        // 创建AsyncAnnotationAdvisor,且传入了 executor
		AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
		if (this.asyncAnnotationType != null) {
   
			advisor.setAsyncAnnotationType(this.asyncAnnotationType);
		}
		advisor.setBeanFactory(beanFactory);
		this.advisor = advisor;
	}

}

看 @Override setBeanFactory ,里面创建了一个AsyncAnnotationAdvisor对象,且参数为我们要重点关注的线程池对象: executor ,点击去看看

5、AsyncAnnotationAdvisor

public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
   

    ...... 忽略其他 ......

	/**
	 * Create a new {@code AsyncAnnotationAdvisor} for the given task executor.
	 * @param executor the task executor to use for asynchronous methods
	 * (can be {@code null} to trigger default executor resolution)
	 * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to
	 * handle unexpected exception thrown by asynchronous method executions
	 * @since 5.1
	 * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory)
	 */
	@SuppressWarnings("unchecked")
	public AsyncAnnotationAdvisor(
			@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
   

		Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);
		asyncAnnotationTypes.add(Async.class);
		try {
   
			asyncAnnotationTypes.add((Class<? extends Annotation>)
					ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));
		}
		catch (ClassNotFoundException ex) {
   
			// If EJB 3.1 API not present, simply ignore.
		}
        // 此处构建 buildAdvice 时用到的executor,要点进去看看方法, 如下
		this.advice = buildAdvice(executor, exceptionHandler);
		this.pointcut = buildPointcut(asyncAnnotationTypes);
	}

    ...... 忽略其他 ......

	protected Advice buildAdvice(
			@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
   
        // 创建了一个拦截器,传了个null进去,要看看拦截器处理了什么
		AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
		// 拦截器中设置了 executor
        interceptor.configure(executor, exceptionHandler);
		return interceptor;
	}

    ...... 忽略其他 ......

}

该构造器主要两个方法,看名字就知道是 AOP 的东西:
this.advice = buildAdvice(executor, exceptionHandler);
this.pointcut = buildPointcut(asyncAnnotationTypes);

切点是定义什么时候触发aop执行advice,先不用管。
重点看 advice ,这才是处理逻辑,创建了一个 拦截器 AnnotationAsyncExecutionInterceptor,点进去看看 拦截器中做了什么

6、AnnotationAsyncExecutionInterceptor

public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionInterceptor {
   

	/**
	 * Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor
	 * and a simple {@link AsyncUncaughtExceptionHandler}.
	 * @param defaultExecutor the executor to be used by default if no more specific
	 * executor has been qualified at the method level using {@link Async#value()};
	 * as of 4.2.6, a local executor for this interceptor will be built otherwise
	 */
    // 直接点进去查看父类
	public AnnotationAsyncExecutionInterceptor(@Nullable Executor defaultExecutor) {
   
		super(defaultExecutor);
	}

    ...... 忽略其他 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值