Spring Boot启动出现死锁
最近公司的一个项目在启动后,在调用往mongoDB写数据时,一直写不进去,运行一段时间后,程序出现内存溢出,jstack导出了线程信息,如下:
mongoDB在插入数据时发生了堵塞,在等待一个监控对象,主线程在调用afterPropertiesSet方法,一直在等待中,表示spring的初始化工作一直没有完成,所以我们结合代码分析,查看afterPropertiesSet方法
@Override public void afterPropertiesSet() throws Exception { scheduleRulePriorityCacheService.start(); if (isTesting == false) { kafkaConsumerTask.start(); executeService.execute(); } }
在spring属性设置完成后,执行了一个kafka消费任务,这个kafka消费任务大致的流程就是从kafka中取出数据,然后放入一个队列中,之后执行了executeService.execute()从队列里面拿出数据然后进行业务处理,我们看看execute()方法
public void execute() { while (true) { try { if (receiveQueue.isEmpty()) { Thread.sleep(200); } else { String msg = receiveQueue.poll(); if (StringUtils.isBlank(msg)) { continue; } poolTaskExecutor.execute(() -> { scheduleProcessCenter.process(msg); }); } } catch (Exception e) { logger.error("ExecuteService error,e:{}", e); } } }
这里就是一个无线循环操作,即如果队列有数据就消费,没有就等待200ms
我们知道,afterPropertiesSet方法是spring Bean的生命周期的一部分,这里发生了死锁,必定是锁住了一个资源,没有释放,而MongoDB又需要这个资源,我看到错误信息,在调用DefaultSingletonBeanRegistry.getSingleton方法时锁住了一个资源,我们查看spring的源代码,
可以看到锁住了singletonObjects对象,这个对象就是spring的单例容器,同样我们可以确定,在MongoDB调用注册事件的时候也需要这个对象,我们同样查看源代码
可以看到MongoDB在注册事件的时候同时需要锁住retrievalMutex对象,那么retrievalMutex和singletonObjects有什么关系?我们接着看
点进去查看getSingletonMutex方法,返回的就是singletonObjects对象,所有retrievalMutex实质和singletonObjects是同一个对象
那么就很容易得出结论了,在调用afterPropertiesSet方法时,singletonObjects对象一直没释放,而MongoDB又需要这个对象,所以产生了死锁。 解决方法,让afterPropertiesSet方法尽快释放singletonObjects对象,我们可以开启一个新线程来执行从队列中读取数据做业务处理的逻辑
public void execute() { poolTaskExecutor.execute(this); } @Override public void run() { while (true) { try { if (receiveQueue.isEmpty()) { Thread.sleep(200); } else { String msg = receiveQueue.poll(); if (StringUtils.isBlank(msg)) { continue; } poolTaskExecutor.execute(() -> { scheduleProcessCenter.process(msg); }); } } catch (Exception e) { logger.error("ExecuteService error,e:{}", e); } } }