当前位置:首页 > 问答 > 正文

Tomcat关掉时数据库连接咋释放,性能优化和资源占用得注意点

当你在Tomcat里用了数据库连接池,比如常见的DBCP2、HikariCP这些,关掉Tomcat的时候,连接能不能正确释放,这事儿挺重要的,如果没弄好,就会留下一些隐患。

Tomcat关闭时,数据库连接是怎么释放的?

核心是靠Tomcat的“生命周期监听”和连接池自己的“清理机制”。(来源:Apache Tomcat官方文档 - 生命周期管理)

当你执行Tomcat的关机命令(比如用shutdown.sh/shutdown.bat脚本,或者在服务器管理界面点停止)时,Tomcat不会一下子硬生生地杀掉所有进程,它会启动一个有序的关闭流程,这个流程里,它会逐个通知它管理的各个组件:“喂,我要关门了,你们自己收拾一下。”

Tomcat关掉时数据库连接咋释放,性能优化和资源占用得注意点

这里面就包括了你的Web应用(Context)和它所用的连接池,连接池组件通常会注册一个“监听器”(Listener),当收到Tomcat发出的“即将关闭”的信号时,这个监听器就会被触发,连接池会做以下几件关键的事:

  1. 不再借出新连接: 它会立刻停止对外提供新的数据库连接,任何新的数据库请求都会失败或等待超时。
  2. 回滚未提交的事务: 它会检查当前所有被应用程序借走但还没还回来的连接,如果这些连接上有还没提交(Commit)的事务,连接池会强制把这些事务回滚(Rollback),这是为了保证数据的一致性,避免留下半拉子工程似的脏数据。(来源:数据库事务处理通用原则)
  3. 强制关闭所有物理连接: 也是最重要的一步,连接池会调用数据库驱动提供的方法,主动、明确地关闭所有它维护的物理数据库连接,它会向数据库服务器发送一个“断开连接”的请求,然后数据库那边就知道这个客户端已经下线了,会回收为这个连接分配的资源(比如内存、进程句柄等)。

只要你是通过Tomcat的正常流程关机,连接池是负责帮你把连接收拾干净的

有哪些情况会导致连接关不掉呢?

Tomcat关掉时数据库连接咋释放,性能优化和资源占用得注意点

最怕的就是“非正常关机”,你不是用Tomcat的shutdown脚本,而是直接在任务管理器里把Tomcat的Java进程(java.exe)给“结束任务”了,这种属于“强杀”,Tomcat的生命周期监听机制根本没机会执行,连接池来不及做任何清理动作,进程就没了,这样一来,从你的应用程序这边看,进程结束了;但从数据库服务器那边看,它还以为客户端好好的,只是暂时没动静(空闲了),数据库会一直保持着这些“僵尸连接”,直到它自己设置的超时时间到了(这个时间可能很长,比如几个小时甚至几天),才会主动把这些连接踢掉,在这段时间里,这些连接一直占着数据库的资源(比如最大连接数名额),如果这种情况频繁发生,就可能把数据库的连接数耗光,导致其他正常应用连不上数据库。

如果你的应用程序代码有Bug,比如发生了内存溢出(OutOfMemoryError)导致Tomcat进程崩溃,效果也和强杀差不多,连接也得不到释放。

性能优化和资源占用需要注意的点

Tomcat关掉时数据库连接咋释放,性能优化和资源占用得注意点

这部分和连接的释放也紧密相关,因为优化不当本身就可能引发资源泄露,进而影响关闭时的清理。

  1. 连接池参数别瞎设: (来源:各连接池官方文档,如HikariCP GitHub Wiki)

    • 最大连接数(maximumPoolSize): 这个值不是越大越好,设得太大会导致数据库同时要处理太多连接,压力山大,反而性能下降,要根据你的应用实际并发量和数据库能承受的能力来设,一般先从一个小值(比如20)开始测试调整。
    • 最小空闲连接(minimumIdle): 这是连接池一开始就预先创建好、保持着的空闲连接数量,如果你的应用访问量波动不大,设一点空闲连接可以加快响应速度,但如果你的应用长时间没人用,维持一大堆空闲连接纯属浪费数据库资源,可以考虑设小一点,或者干脆设为0,让连接池按需创建。
    • 连接最大存活时间(maxLifetime): 这个特别重要!一定要设置一个合理的值(比如30分钟或1小时),即使你的应用程序代码写得天衣无缝,也难保网络抖动或者数据库端偶尔出问题,导致某个连接变得不可用但连接池自己没检测出来,设置一个最大存活时间,能强制定期更换连接,相当于一种自我修复机制,避免使用陈旧的、可能已经失效的连接,这在Tomcat重启时也能帮助清理“上一代”的连接。
    • 空闲超时(idleTimeout): 如果一个空闲连接超过这个时间都没被用过,连接池就会把它关掉,缩减小资源占用,这个值应该小于数据库端的“等待超时”时间。
  2. 应用程序代码要守规矩: (来源:Java数据库编程规范)

    • 借了东西一定要还: 这是最最最重要的原则,你的代码里,只要用DataSource.getConnection()拿到了一个连接,就必须在finally代码块里调用connection.close()把它还回去,注意,这里的close()在连接池环境下并不是真关闭物理连接,只是告诉连接池“我用完了,你收回去吧”,如果你不还,连接池就以为你还在用,这个连接会一直被占用,直到你的会话结束或超时,这就是“连接泄露”,泄露多了,连接池的连接就会被借光,导致后续请求卡住,Tomcat关机时,连接池也很难强制回收这些“失联”的连接。
    • 及时关闭Statement和ResultSet: 和连接类似,用完这些对象也要及时关闭,释放它们占用的游标等资源。
  3. 定期监控和排查:

    • 看Tomcat日志: 关注有没有连接泄露的警告信息,像DBCP2等连接池可以配置输出泄露警告日志。
    • 看数据库: 定期登录数据库,执行像MySQL的SHOW PROCESSLIST或Oracle的SELECT * FROM V$SESSION这样的命令,看看当前有哪些活跃的连接,它们来自哪里、空闲了多久,如果发现大量来自你Tomcat应用的、长时间空闲的连接,那很可能就是泄露了。

Tomcat正常关机时连接释放基本是自动的,但你得保证:第一,用正常方式关机;第二,连接池参数配置合理;第三,应用程序代码没有泄露连接的Bug,做好这三点,就能在保证性能的同时,确保资源(特别是数据库侧的连接资源)得到妥善管理,避免因为连接不当占用导致的各种古怪问题。