环境说明:
spring-boot 2.3.1
jdk8
apache-dubbo 2.7.1
原因:
dubbo中,提前注册shutdownHook导致死锁问题
业务场景:
项目需要在启动时,缓存一些业务数据,所以在利用相关bean实现InitializingBean接口,实现afterPropertiesSet()方法,如果在afterPropertiesSet()方法缓存数据出现异常,则使用System.exit()
方法退出JVM,并且该bean是需要提前实例化的。
具体代码分析:
org.springframework.context.support.AbstractApplicationContext中
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// 实例化非懒加载的对象。这步会牵涉到dubbo去生成dubbo代理对象,导致提前注册shutdownHook
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
dubbo导致的提前注入shutdownHook
本来应该是刷新springContext完成之后进行的,如下
private void refreshContext(ConfigurableApplicationContext context) {
refresh((ApplicationContext) context);
//refresh完成之后,注册hook,
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
org.springframework.boot.SpringApplication
上述注册hook前置之后,并且在需要提前实例化的bean创建过程中,触发了System.exit(),导致JVM去调用shutdownHook,而之前由Main线程持有的startupShutdownMonitor对象是没有释放的(需要refreshContext执行完之后才会释放),现在main线程由于System.exit()去调用runHooks()方法,如下图:
而SpringContextShutdownHook线程又需要去获取startupShutdownMonitor锁,导致无法获取执行完成,所以,hook.join()方法就一值将main阻塞下去,导致JVM无法退出。
此刻,main线程和SpringContextShutdownHook线程的状态分别如下:
main线程:
SpringContextShutdownHook线程:
解决办法
- 将apache-dubbo进行升级,尝试了下2.7.14,是可以的