升到 Spring Boot 4.1,虚拟线程开了,HikariCP 连接池却崩了

发布时间:2026/6/29 21:10:46
升到 Spring Boot 4.1,虚拟线程开了,HikariCP 连接池却崩了 .0-RC1 在 2026 年 4 月 23 日正式发布包含 113 个改进。如果你在维护 3.x 的生产服务现在是做技术决策的窗口期。这篇文章的核心问题只有一个升不升升了有什么好处升坏了怎么办。一句话摘要Spring Boot 4.1 最值得升的是虚拟线程 LazyConnection 双剑合并带来的吞吐量提升但必须同步调低 HikariCP 的maximumPoolSize否则适得其反。为什么 4.1 是个重要的升级窗口Spring Boot 4.0 是破坏性最强的一次大版本跃迁——它要求 Java 17、Jakarta EE 11、Spring Framework 7连 Jackson 都从com.fasterxml.jackson搬家到tools.jackson。如果你还在 3.x跨过 4.0 的代价已经很高了。4.1 不一样。4.1 是在 4.0 底座上的增强版破坏性变更极少主要是把 4.0 里还需要手动配置的能力变成「默认开启」。这意味着从 4.0 升到 4.1 的成本远低于从 3.x 升到 4.0。但如果你还在 3.x现在的问题是3.5.x 是最后一个 3.x 大版本进入维护窗口期不会太远。4.1 RC1 的发布意味着稳定版 GA 很快就会到来。现在做技术预研总比到时候被迫升级要从容得多。Spring Boot 4.x 整个版本线的依赖基线如下看完你就明白为什么说「跨度很大」组件Spring Boot 3.xSpring Boot 4.0Spring Boot 4.1Java 最低要求Java 17Java 17Java 17Spring Framework6.x7.07.0Jakarta EE10Servlet 5.011Servlet 6.111Servlet 6.1Jackson2.x3.0组件迁移3.xHibernate6.x7.17.xKotlin1.92.22.34.0 → 4.1 几乎没有追加破坏性变更这是关键。4.1 的主要新增内容虚拟线程成为 Java 21 环境的默认选项LazyConnectionDataSourceProxy自动集成连接惰性获取Redis 注解驱动监听器RedisListener自动配置HTTP Client SSRF 防护InetAddressFiltergRPCGrpcAdvice异常处理支持OpenTelemetry 环境变量读取Spring Framework 7.0.7、Spring Security 7.1.0-RC1、Micrometer 1.17.0-RC1图Spring Boot 4.x 版本线依赖基线对比3.x→4.0 是最大跨越4.0→4.1 几乎无破坏性变更虚拟线程默认开启好处和陷阱这是 4.1 最重要的改变也是最容易踩坑的地方必须单独讲透。什么叫「平台线程」和「虚拟线程」传统 Java 线程平台线程和操作系统线程是 1:1 绑定的。创建一个线程OS 就要分配一个内核线程每个线程默认栈大小 512KB1MB。一台 8GB 内存的机器理论上最多能撑几千个线程。Tomcat 的maxThreads默认是 200就是因为超过这个数字线程上下文切换的开销会比 IO 等待的收益更大。虚拟线程是 Java 21 引入的 Project Loom 成果。它是 JVM 层面的线程不和 OS 线程 1:1 绑定。当虚拟线程执行到阻塞操作IO、数据库查询、Thread.sleep时JVM 会自动把它「卸载」到载体线程carrier thread让载体线程去做别的事等 IO 完成再重新挂载。整个过程对代码完全透明你的synchronized、Thread.currentThread()照样能用。实际效果同样的机器用虚拟线程可以轻松维持数万个并发「连接」因为绝大多数线程都在等 IO不占 CPU。Spring Boot 4.1 在 Java 21 环境下自动用虚拟线程替换 Tomcat 的工作线程池相当于配置了spring.threads.virtual.enabledtrue。你不需要改任何代码。陷阱在哪里问题出在数据库连接池。HikariCP 的设计哲学是连接数越少越好。因为数据库端的并发连接本身就是昂贵资源HikariCP 的maximumPoolSize默认是 10官方文档甚至建议单个服务不要超过 20-30。传统架构下这个设计很合理你只有 200 个 Tomcat 线程最多 200 个并发请求200 个请求也不可能同时在做数据库操作所以 10-20 个连接够用。虚拟线程改变了这个前提。当你用虚拟线程后Tomcat 可以同时接受数万个请求。假设有 5000 个请求同时到达全都要查数据库——HikariCP 的连接池只有 10 个剩下 4990 个请求全部挂起等待连接等待超时时间到了默认 30 秒一波SQLTimeoutException涌出来。这就是文章开头那个问题的根源虚拟线程让 IO 并发能力提升了 100 倍但连接池的容量没有跟上。修复方案有两个方向不是二选一最好同时做方向一调高 HikariCP 连接数上限spring: datasource: hikari: # 虚拟线程环境下适当提高连接数 # 但不要无限拉高——数据库端连接是有限的 maximum-pool-size: 50 # 连接等待超时适当缩短快速失败而非长时间堵塞 connection-timeout: 3000 # 连接最大生命周期防止数据库侧超时 max-lifetime: 1800000方向二启用LazyConnectionDataSourceProxy4.1 新功能Spring Boot 4.1 内置了对LazyConnectionDataSourceProxy的自动集成通过配置spring.datasource.connection-fetch属性启用。惰性连接的意思是只有当你真正执行 SQL 语句时才从连接池获取物理连接而不是在事务开始时就占用一个连接。spring: datasource: # Spring Boot 4.1 新增启用惰性连接获取 connection-fetch: lazy这个改动看起来小但效果显著对于那些开启了事务但不一定会执行数据库操作的代码路径比如先读缓存缓存命中就直接返回可以大幅减少无效的连接占用。组合策略连接池调到 50同时开启 lazy connection加上虚拟线程实际上你可以用比传统架构少 3/4 的连接数服务更多的并发请求。图虚拟线程下数万请求 vs HikariCP 默认 10 个连接的根本冲突有一个更隐蔽的坑synchronized锁钉死虚拟线程有一个已知限制如果虚拟线程在持有synchronized锁的情况下遇到阻塞它无法被 JVM 卸载会直接占用载体线程pinning。这意味着那段时间这个载体线程完全被堵死无法执行其他虚拟线程。Java 21 里这还是个问题Java 24 已经修复了synchronized的 pinning 问题但如果你在用 Java 21需要留意项目里的第三方库如果大量使用synchronized比如某些老版本 JDBC 驱动在虚拟线程下表现可能不如预期可以通过 JVM 参数-Djdk.tracePinnedThreadsfull来检测 pinning 发生的位置3.x 升到 4.x不兼容变更清单如果你现在在 3.x计划升到 4.1必须过一遍这张清单。有些变更没有错误提示只会在运行时悄悄行为异常。测试框架MockBean和SpringBootTest行为变了这是最容易踩的坑因为它不会在编译时报错。// Spring Boot 3.x这样写可以 MockBean private UserService userService; // Spring Boot 4.x必须改成 MockitoBean private UserService userService;更严重的是SpringBootTest的行为变了。在 3.x 里SpringBootTest会自动注入MockMvc和TestRestTemplate。在 4.x 里你必须明确加注解// 3.x 写法4.x 里这样写MockMvc 是 null但不报错 SpringBootTest class UserControllerTest { Autowired MockMvc mockMvc; // 会是 null调用时才 NPE } // 4.x 必须这样写 SpringBootTest AutoConfigureMockMvc // 必须加这个 class UserControllerTest { Autowired MockMvc mockMvc; // 这才有值 }Jackson从 2.x 迁到 3.x组件 ID 变了如果你在 pom.xml 里直接依赖了 Jackson 模块4.x 要改 Group ID!-- Spring Boot 3.x -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency !-- Spring Boot 4.xJackson 3.x -- dependency groupIdtools.jackson.core/groupId artifactIdjackson-databind/artifactId /dependencySpring Boot 4.0 的 starter 已经帮你引入了正确版本但如果你有直接依赖要手动改。另外 Jackson 的注解名也有变化JsonComponent改为JacksonComponent。MongoDB 配置命名空间调整# Spring Boot 3.x spring: data: mongodb: uri: mongodb://localhost:27017/mydb # Spring Boot 4.x spring: mongodb: uri: mongodb://localhost:27017/mydb只有 Spring Data MongoDB 专属的配置还在spring.data.mongodb.*下连接相关的基础配置全部搬到spring.mongodb.*。健康探针默认开启了# Spring Boot 3.x默认关闭需要手动开 management: endpoint: health: probes: enabled: true # Spring Boot 4.x默认已开启 # 如果你不想要反而需要手动关 management: endpoint: health: probes: enabled: false如果你的 Kubernetes Pod 没有配 livenessProbe 和 readinessProbe升级后 Spring Boot 会自动暴露/actuator/health/liveness和/actuator/health/readiness。这通常是好事但如果你的基础设施有严格的 Actuator 访问控制要提前确认权限策略。server.error.*属性迁移# Spring Boot 3.x server: error: include-message: always include-stacktrace: never # Spring Boot 4.x spring: web: error: include-message: always include-stacktrace: never官方提供了spring-boot-properties-migrator工具加到 pom.xml 里可以在运行时自动识别废弃配置并给出迁移提示dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-properties-migrator/artifactId scoperuntime/scope /dependency记得迁移完成后要把这个依赖删掉别带上生产。图6 类关键不兼容变更速查红色标注最容易踩坑的静默失效场景AOT 编译和 GraalVM 原生镜像进展如何这块功能对大多数业务团队来说还比较遥远但有几个进展值得记录。Spring Boot 4.x 系列在 AOTAhead-Of-Time编译上持续投入。AOT 的核心价值是在编译阶段把 Spring 的 Bean 注册、条件判断、依赖注入等运行时逻辑预先生成为静态代码这样用 GraalVM 编译成原生镜像后启动速度可以从秒级降到毫秒级内存占用也能减少 50-80%。4.1 对 GraalVM 的要求是 native-image v25Spring Boot 4.0 就开始要求这个了。适合使用原生镜像的场景Serverless / FaaS 函数冷启动时间是核心指标边缘计算节点内存限制严格CLI 工具需要快速启动不适合的场景长期运行的业务服务JIT 预热后的性能往往优于 AOT频繁变更的服务每次改代码都要重新编译原生镜像几十分钟起有大量反射操作的遗留代码AOT 需要手动声明反射元数据改造成本高坦白说对于大多数维护中等规模 Spring Boot 服务的团队现阶段上原生镜像的收益不够明显维护成本太高。虚拟线程才是 4.x 最值得立即用起来的改进。迁移路径最稳妥的升级策略直接从 3.3/3.4 跳到 4.1 RC1 对生产服务来说风险太大。建议按这个顺序走第一步先升到 3.5.x3.5.x 是 3.x 的最后一个大版本它会把 4.0 的许多不兼容变更以「废弃」形式提前暴露出来。这步的目标是让编译器帮你找出所有会在 4.x 里出问题的代码。!-- pom.xml -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.5.0/version /parent升到 3.5 后打开所有 deprecation warning把所有废弃 API 的调用全部清掉MockBean→MockitoBean等确保 0 警告。第二步升到 4.0.x当前稳定版 4.0.64.0 是最大的跨越主要工作Jackson Group ID 迁移配置命名空间调整用 properties-migrator 工具辅助测试代码的SpringBootTest检查删掉 Undertow如果你在用的话这一步建议只在非生产环境做做完全量测试。第三步升到 4.1.x目前是 RC1GA 版很快到来4.1 从 4.0 升基本无破坏性变更主要是加功能。升完后重点检查连接池配置maximum-pool-size需要复估开启spring.datasource.connection-fetch: lazy在 Java 21 环境下确认虚拟线程已自动启用可以看日志确认回滚策略万一升级后出问题最快的回滚方式是版本号回滚不要动业务代码。所以升级时要确保pom.xml 里的 Spring Boot 版本是唯一控制入口不要散落 spring-framework.version 等 overrides升级前跑完全量集成测试记录基线响应时间和连接池指标上线后监控 HikariCP 连接等待时间hikaricp.connections.pending和连接超时次数我的判断Spring Boot 4.1 值得升但要搞清楚先后顺序。如果你现在在 3.x当前最重要的动作是把代码里所有对废弃 API 的调用清掉这一步不管你升不升 4.x 都应该做做了就是降低技术债。如果你已经在 4.0升 4.1 几乎是白赚——连接惰性获取、Redis 注解监听器、SSRF 防护这些功能不升你就得手动配升了就直接有了。唯一要做的事是检查连接池配置。虚拟线程的真实收益取决于你的服务是否 IO 密集。如果你的服务主要是 CPU 密集型计算图像处理、加解密、复杂规则引擎虚拟线程帮助有限如果你的服务大量时间花在等数据库返回、等下游 HTTP 接口、等 Redis 响应那虚拟线程在不增加机器的情况下能明显提升吞吐量。说到底框架升级这件事没有时间表压力的话最好的策略是先在一个非核心服务上试水跑一个月把坑踩完再推广到核心链路。强行「全部一起升」往往是生产事故的温床。下一篇打算深入拆解 Spring Boot 4.x 里 AOT 编译的实现机制——它到底在编译阶段生成了什么代码为什么能让原生镜像启动这么快。感兴趣的话关注一下发布了会第一时间推送。如果你的团队正在评估 4.x 升级计划这篇可以直接甩给负责升级的工程师做前置阅读省得他踩到虚拟线程和连接池这个坑。常见问题QJava 17 能用虚拟线程吗必须 Java 21 才行A必须 Java 21。虚拟线程在 Java 19/20 是 Preview 特性Java 21 才是正式稳定版本。Spring Boot 4.x 的虚拟线程自动配置只在 Java 21 环境下生效Java 17/21 只是最低要求不代表 Java 17 能用虚拟线程。QHikariCP 的maximumPoolSize设多少合适A没有万能答案取决于你的数据库能接受多少并发连接。通用建议先用公式(核心数 × 2) 磁盘并发数作为起点这是 HikariCP 官方建议对于支持虚拟线程的场景可以适当提高到 30-50但绝对不要盲目提到 200。数据库端的连接是有成本的连接数越多数据库内存开销越大反而会拖慢总体响应。Q升级后原来用spring.mvc.async.request-timeout控制异步超时的配置还有效吗A4.x 里这个属性没有被移除但行为有细微变化。虚拟线程下请求处理变成了同步阻塞模式虽然底层是异步 IO所以request-timeout主要影响的是同步请求的超时控制。如果你之前依赖 WebFlux 的响应式超时建议用Mono.timeout()在代码层面显式控制。Q生产环境能直接用 RC1 吗A不建议。RC1 的意思是 Release Candidate功能冻结了但还在做 bug 修复不会追加破坏性变更。现在做技术预研、搭测试环境是合理的但生产还是等 GA 版本一般 RC1 发布后 2-4 周会出 GA。Q有没有办法在 4.x 里把虚拟线程关掉先「静默升级」A有。在application.yml里明确配置spring: threads: virtual: enabled: false