测试环境某个接口没响应,查看系统日志提示java.lang.OutOfMemoryError: Java heap space
居然OOM,看下GC日志,发现每秒执行一次FullGC。
由于设置了-XX:+HeapDumpOnOutOfMemoryError 参数,所以能直接拿到堆文件。使用MAT工具看了下,发现创建了大量的 AnnotationConfigApplicationContext 对象,且beanDefinitionMap都是些配置类。
看起来是开启了Devtools的热部署,Restarter对象持有了大量的AnnotationConfigApplicationContext 对象导致的,那为何会产生大量的对象呢?我试图在系统日志中寻找一些蛛丝马迹,发现有大量报错日志
报错Cause:
根据调用轨迹,报错原因是执行SeataDataSourceBeanPostProcessor的proxyDataSource方法出错
如果seata设置自动代理数据源,那么bean的初始化过程会执行该处理器
为何创建代理对象会报错?根据报错原因在网上查了下资料,发现这篇文章使用 Cglib ,Javassist 实现多重代理 解决:Caused by: java.lang.ClassFormatError: Duplicate method name...-CSDN博客
那第一次代理对象是什么时候创建的呢?带着疑问本地调试了下,原来是在系统启动过程中,执行BeanPostProcessor给数据源创建代理对象。
bean的初始化阶段完成后会把该代理放进IOC容器中。
那第二次代理对象又是什么时候创建的呢?分析报错日志,发现是nacos更新配置中心内容过程中触发的第二次代理对象创建,因创建失败导致报错。为了分析nacos更新配置过程中都做了什么,进行源码阅读。
ClientWorker会不断轮询服务端看是否有配置更新
当发现有更新内容就会发布一个RefreshEvent事件
触发RefreshEventListener监听器执行Bean刷新
继续跟踪刷新过程,发现会执行一个addConfigFilesToEnvironment方法,该方法内会重新执行SpringApplication#run( args )方法。
run方法的执行逻辑就是我们熟悉的IOC容器创建过程了,由于设置的WebApplicationType是NONE类型,所以创建了默认的AnnotationConfigApplicationContext对象
执行完prepareContext方法会发布一个ApplicationPreparedEvent事件
由于项目中引入了DevTools热部署工具,所以触发了RestartApplicationListener监听器
最后把applicationContext添加到Restarter#rootContexts列表中,而这个applicationContext的类型就是前面创建的默认类型AnnotationConfigApplicationContext。
到此,弄明白了AnnotationConfigApplicationContext是如何被Restarter引用的,但还没触发报错,继续跟踪。
执行完addConfigFilesToEnvironment()方法后,会获取要变更的属性项,然后发布一个EnvironmentChangeEvent事件。
该事件会被ConfigurationPropertiesRebinder监听,执行rebind()方法,rebind方法会把有@ConfigurationProperties注解的配置类重新初始化一遍,由于项目使用的是默认数据源,且有@ConfigurationProperties注解所以dataSource需要重新初始化
执行初始化之前先从容器中获取对应的Bean,这个Bean就是SeataDataSourceBeanPostProcessor第一次给DataSource创建的代理对象。
然后就是执行标准的bean初始化流程了
由于开了自动代理数据源,所以再次执行SeataDataSourceBeanPostProcessor。
注意: !(bean instanceof DataSourceProxy) 判断条件为true,所以再次执行创建代理对象,最后触发报错(不能给CGLIB对象创建代理对象,前文已有解释链接)。
异常被com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable线程内部捕捉到,延迟2000ms后再次执行上述流程。
所以产生大量AnnotationConfigApplicationContext对象的原因就是:
创建对象 --> 放进Restarter --> 执行SeataDataSourceBeanPostProcessor --> 捕捉报错再次执行
Restarter持有AnnotationConfigApplicationContext对象引用,所以GC无法对这些对象进行垃圾回收,直到堆空间耗尽抛出OOM异常。
解决方案:
1:禁用devtools热部署
2:seata禁用自动代理数据源,改为手动声明