千家信息网

Tomcat 9.0.26 高并发场景下DeadLock问题

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,本文首发于 vivo互联网技术 微信公众号链接:https://mp.weixin.qq.com/s/-OcCDI4L5GR8vVXSYhXJ7w作者:黄卫兵、陈锦霞一、Tomcat容器 9.0.26
千家信息网最后更新 2026年10月31日Tomcat 9.0.26 高并发场景下DeadLock问题

本文首发于 vivo互联网技术 微信公众号
链接:https://mp.weixin.qq.com/s/-OcCDI4L5GR8vVXSYhXJ7w
作者:黄卫兵、陈锦霞

一、Tomcat容器 9.0.26 版本 Deadlock 问题

1.1 问题现象

1.1.1 发生 Deadlock 的背景

某接口/get.do压测,3分钟后,成功事务数TPS由1W骤降至0。

1.1.2 Tomcat服务器出现大量的CLOSE_WAIT

被压测服务器,出现TCP CLOSE_WAIT状态个数在200~2W左右。

1.2 初步定位:线程堆栈信息入手

通过jstack打印Tomcat堆栈信息,发现"Found 1 deadlock"

Found one Java-level deadlock:============================="http-nio-8080-exec-409":waiting to lock monitor 0x00007f064805aa78 (object 0x00000006c0ebf148, a java.util.HashSet),which is held by "http-nio-8080-ClientPoller""http-nio-8080-ClientPoller":waiting to lock monitor 0x00007f05e8061058 (object 0x00000007bfe40a70, a java.lang.Object),which is held by "http-nio-8080-exec-205""http-nio-8080-exec-205":waiting to lock monitor 0x00007f0614018448 (object 0x00000006c0e8e088, a java.util.HashSet),which is held by "http-nio-8080-BlockPoller""http-nio-8080-BlockPoller":waiting to lock monitor 0x0000000001ed06e8 (object 0x00000007bfe110f8, a java.lang.Object),which is held by "http-nio-8080-exec-380""http-nio-8080-exec-380":waiting to lock monitor 0x00007f064805aa78 (object 0x00000006c0ebf148, a java.util.HashSet),which is held by "http-nio-8080-ClientPoller"
1.2.1 快速修复方案

内部讨论后,认为当前Tomcat版本可能有Bug。不影响项目进度,简单修改方案把SpringBoot 使用的Tomcat 9.0.26 降级到Tomcat 8。降级后再次压测,没有发现问题。基本上可以确定Tomcat 9.0.26 应该是存在 Deadlock 问题。

1.3 问题进一步跟踪

1.3.1 向Apache社区的反馈

为了确认问题,我们试着给Tomcat提交Bug反馈。

从堆栈信息来看,是3类线程5个线程由于加锁的顺序不致,从而相互等待发生了死锁。图形化上面加锁的过程如下图。

1.4 问题原因分析

明确了死锁的过程,但是哪个环节出了问题呢。这就需要深入到源码层去定位问题。首先需要下载OpenJDK 源码,然后是Tomcat 9.0.26 的源码。根据堆栈信息,定位到相应的代码位置。我们理出如下图Tomcat 9.0.26死锁流程说明。

要比较好的理解上图,需要对于NIO有一定的了解。在Tomcat中NIO主要是理解NIO Endpoint。

Poller是对于Selector的一个封装,而线程名为exec-xx的执行线程是Channel的封装。在NIO中Channel注册到Selector然后通过SelectionKey来记录对应关系。到此,主角都上场了。

Poller的run方法作为后台线程一直在轮询(select)准备好的SelectionKey,在轮询的时候也顺便需要把cancelledKey中的SelectionKey给反注册。执行线程EXEC-XX在处理时会先判断连接的状态,比如失败、异常等情况会调用Channel的close方法去关闭连接。

而Channel的close实际只是把SelectionKey加入到cancelledKey。两者都需要先锁定,但锁定的顺序不一致,从而导致死锁。

1.4.1 与Tomcat开发者的交流

在提交Bug后,很快得到了Remy Maucherat的回复,首先他提到这个NIO内部的死锁。然后我们提到NIO内部的死锁是由于Poller.run和Poller.canceledKey在并发时导到的。

Remy Maucherat很快就进行了修复,主要是把Poller.canceledKey中close移到了finally中去执行,也就是先让Poller.run获得锁。

在得到修复后,我们使用替换后的代码进行了再次压测,死锁问题没有出现了。Remy Maucherat同时提到在最新的OpenJDK中相关问题的修复,但只会出现在jdk 11和14版本。

沟通中的详情见下图。

1.4.2 Github上修复的验证

https://github.com/apache/tomcat/commit/9b1a8b67bffe462fc745b19e15ed59c37e2e1dcf

1.5 结果验证

使用 https://github.com/apache/tomcat/commit/9b1a8b67bffe462fc745b19e15ed59c37e2e1dcf 提供修复后代码,重新打包tomcat-embed-core.jar 替换9.X.XX的再次压测,TPS平稳在1.5W左右。

到此问题基本是定位清楚,并得到了修复。Remy Maucherat也回复到"The fix will be in Tomcat 9.0.31+"。

目前Tomcat 最新版本是Tomcat 9.0.30,还需要耐心等待31版本更新。建议使用Tomcat 8版本。

二、相关链接与参考

  1. OpenJdk源码下载

  2. Tomcat 源码

  3. 来自阿里云溪社区:断网故障时Mtop触发Tomcat高并发场景下的BUG排查和修复

  4. 深度解读Tomcat中的NIO模型
问题 线程 死锁 版本 源码 信息 堆栈 定位 再次 代码 很快 方案 方法 服务器 状态 社区 过程 链接 顺序 封装 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网金融科技 互联网加科技 中国互联网科技工作委员会毛军 互联网科技排名 善班互联网科技有限公司 济南和联互联网科技有限公司 互联网企业和科技型企业 金融科技与互联网金融相同和不同 全球顶尖互联网科技公司 上海宁丞科技互联网 杭州壁岐互联网科技有限公司 注册互联网科技公司经营范围大全 深圳市罗太互联网科技有限公司 工业互联网山东科技大学 科技互联网海报 上海科技产业互联网平台 银行科技岗属于互联网吗 陕西九四连强互联网科技有限公司 优雇互联网科技有限公司 银川新华互联网科技学校招聘 互联网佳科技有限公司 科技和互联网公司 互联网巨头反垄断金融科技 科技服务与互联网 合肥互联网科技公司 奇酷互联网络科技公司 中国最先进互联网科技公司 互联网科技与大数据专业 广州千川互联网科技有限公司 互联网高科技绘画
0