写了 10年 Java 才悟出来:Spring Boot “变快”的真相,不在代码

12333社保查询网www.sz12333.net.cn 2026-02-15来源:人力资源和社会保障局

  我见过太多 Spring Boot 项目:功能没问题、测试全绿、线上也能跑——但一到压测就“像糊在蜂蜜里”。后来我才慢慢意识到:Spring Boot 并不慢,慢的是我们把“默认值”当成了“最佳实践”。

  这篇我按“真实踩坑路径”把常见的性能坑拆开讲:从数据库、线程、序列化、启动、JVM、到观测与配置。每一节都给你能落地的动作,你拿去对照自己的项目做一轮体检,效果通常比你想象更明显。

0)先立规矩:别用“感觉”,用“证据”

  优化前先把基线立起来,否则你永远在“我觉得快了”。

  最少做三件事:

  • 指标:P50 / P95 / P99、RPS、CPU、GC Pause、DB QPS、连接池等待时间
  • 链路:接口耗时拆分(Controller / Service / DB / 外部调用)
  • 对照:同一份压测脚本、同一份数据量、同一份机器规格

      如果你现在还没有“应用内部视角”,优先把 Actuator + Micrometer + Prometheus/Grafana 接上,别继续盲飞。

    1)数据库:你以为是 “一次查询”,其实是 “101 次查询”典型症状

      列表接口返回 100 条用户,每条用户都要读 orders、roles、tags……然后你写了 getOrders()。

      结果 Hibernate 默默给你打出 N+1:1 次查用户 + N 次查订单 = 延迟直线上升。

    解决思路(别只会“全改 EAGER”)

      我更推荐三个工具组合,按场景选:

      A. EntityGraph:声明式把某次查询变“带上关联”

      @Entity@NamedEntityGraph( name = "User.orders", attributeNodes = @NamedAttributeNode("orders"))class User { ... }@EntityGraph("User.orders")List findAllWithOrders();

      B. JOIN FETCH:需要强控制时更直接

      @Query("select u from User u join fetch u.orders where u.id in :ids")List findAllWithOrders(@Param("ids") List ids);

      C. BatchSize:必须 lazy,但想“批量懒加载”

      @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")@BatchSize(size = 25)private List orders;

      一句话标准:

  • 列表页:宁可 join fetch / entity graph,一次取齐
  • 详情页:可以分层 DTO + 精准查询
  • 超大数据:分页 + 批量策略,别“全拉爆”2)连接池:默认 10 个连接,够谁用?

      很多人把 HikariCP 当“自带就不用管”,然后线上高峰出现:

  • 请求排队、线程卡住
  • DB 明明没满,应用却在等连接

      默认池大小通常比较保守;在并发请求 + DB 操作偏多的系统里,连接池就是吞吐量阀门。

    你可以这样调一个“可用起点”

      spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 20000 idle-timeout: 300000 max-lifetime: 1200000 leak-detection-threshold: 60000更关键的“思考方式”

      池大小不是越大越好:

  • 应用端大了:DB 扛不住、锁竞争更惨
  • 小了:应用端排队、延迟飙升

      一个务实的策略是:先压测看“等待连接”的比例 → 再逐步调大到“等待基本消失但 DB 仍稳定”的点。

    3)缓存:不是“全加 @Cacheable 就快”

      我也干过这种事:凡是查 DB 的方法都 @Cacheable。最后发现:

  • 热点小对象缓存后更慢(序列化/反序列化、锁、淘汰)
  • 数据频繁变更导致缓存抖动
  • 缓存穿透把 DB 又打穿

      缓存的本质是:用空间换时间,但前提是“命中”和“稳定”。

    我自己的规则(很朴素但好用)

      只缓存同时满足:

  • 获取/计算 > 50ms
  • 访问频繁
  • 变更不那么频繁
  • 能接受短暂不一致

      另外,别把“派生字段”单独再缓存一次,应该复用主对象缓存。

    4)JSON 序列化:你返回的是“实体”,输出的是“整个世界”

      把 JPA Entity 直接当 API Response,Jackson 往往会:

  • 序列化一堆你根本不想给前端的字段
  • 触发懒加载(又回到 N+1)
  • 返回体巨大,网络与序列化双杀

      解决方式:DTO。别偷懒。DTO 不只是性能,它还是 API 契约隔离。

      record UserSummaryDTO(Long id, String name) {}@GetMapping("/users/{id}/summary")UserSummaryDTO summary(@PathVariable Long id) { var u = userService.findById(id); return new UserSummaryDTO(u.getId(), u.getName());}5)异步:别让用户等“非关键路径”

      注册接口里最常见的慢操作:发邮件、打点、发 MQ、刷缓存……这些不影响“主流程成功”的动作,就不要阻塞响应。

      Spring 的 @Async + 明确线程池,是最简单的提速手段之一。

      @EnableAsync@Configurationclass AsyncConfig { @Bean("appExecutor") Executor appExecutor() { var ex = new ThreadPoolTaskExecutor(); ex.setCorePoolSize(5); ex.setMaxPoolSize(10); ex.setQueueCapacity(200); ex.setThreadNamePrefix("async-"); ex.initialize(); return ex; }}@Async("appExecutor")public void sendWelcomeEmail(...) { ... }

      额外一句:如果你在 Java 21 + Spring Boot 新版本上,虚拟线程也值得评估,但不要把它当“银弹”。异步边界、IO 依赖、DB 池大小依然决定上限。

    6)启动慢:不是“Spring 太重”,是你让它“全加载”

      项目一大,启动慢通常来自两件事:

  • 扫描范围太大
  • Bean 过早初始化(eager)三个立竿见影的动作

      A. 缩小扫描包

      @SpringBootApplication(scanBasePackages = { "com.xxx.controller", "com.xxx.service", "com.xxx.config"})class App {}

      B. 开启 Lazy Init(谨慎:首次调用会变慢)

      spring: main: lazy-initialization: true

      C. spring-context-indexer(大项目很香)它会在构建期预计算候选组件,减少运行期扫描与反射开销。

    进阶:GraalVM Native Image / CRaC(看你的部署形态)

      如果你是大量小服务、弹性伸缩、冷启动敏感:Native Image 的收益往往非常直观,但你要接受构建与兼容性成本。

    7)JVM:你以为是“内存泄漏”,其实是“GC 在求救”

      很多“内存问题”本质是:我见过太多 Spring Boot 项目:功能没问题、测试全绿、线上也能跑——但一到压测就“像糊在蜂蜜里”。后来我才慢慢意识到:Spring Boot 并不慢,慢的是我们把“默认值”当成了“最佳实践”。

      这篇我按“真实踩坑路径”把常见的性能坑拆开讲:从数据库、线程、序列化、启动、JVM、到观测与配置。每一节都给你能落地的动作,你拿去对照自己的项目做一轮体检,效果通常比你想象更明显。

    0)先立规矩:别用“感觉”,用“证据”

      优化前先把基线立起来,否则你永远在“我觉得快了”。

      最少做三件事:

  • 指标:P50 / P95 / P99、RPS、CPU、GC Pause、DB QPS、连接池等待时间
  • 链路:接口耗时拆分(Controller / Service / DB / 外部调用)
  • 对照:同一份压测脚本、同一份数据量、同一份机器规格

      如果你现在还没有“应用内部视角”,优先把 Actuator + Micrometer + Prometheus/Grafana 接上,别继续盲飞。

    1)数据库:你以为是 “一次查询”,其实是 “101 次查询”典型症状

      列表接口返回 100 条用户,每条用户都要读 orders、roles、tags……然后你写了 getOrders()。

      结果 Hibernate 默默给你打出 N+1:1 次查用户 + N 次查订单 = 延迟直线上升。

    解决思路(别只会“全改 EAGER”)

      我更推荐三个工具组合,按场景选:

      A. EntityGraph:声明式把某次查询变“带上关联”

      @Entity@NamedEntityGraph( name = "User.orders", attributeNodes = @NamedAttributeNode("orders"))class User { ... }@EntityGraph("User.orders")List findAllWithOrders();

      B. JOIN FETCH:需要强控制时更直接

      @Query("select u from User u join fetch u.orders where u.id in :ids")List findAllWithOrders(@Param("ids") List ids);

      C. BatchSize:必须 lazy,但想“批量懒加载”

      @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")@BatchSize(size = 25)private List orders;

      一句话标准:

  • 列表页:宁可 join fetch / entity graph,一次取齐
  • 详情页:可以分层 DTO + 精准查询
  • 超大数据:分页 + 批量策略,别“全拉爆”2)连接池:默认 10 个连接,够谁用?

      很多人把 HikariCP 当“自带就不用管”,然后线上高峰出现:

  • 请求排队、线程卡住
  • DB 明明没满,应用却在等连接

      默认池大小通常比较保守;在并发请求 + DB 操作偏多的系统里,连接池就是吞吐量阀门。

    你可以这样调一个“可用起点”

      spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 20000 idle-timeout: 300000 max-lifetime: 1200000 leak-detection-threshold: 60000更关键的“思考方式”

      池大小不是越大越好:

  • 应用端大了:DB 扛不住、锁竞争更惨
  • 小了:应用端排队、延迟飙升

      一个务实的策略是:先压测看“等待连接”的比例 → 再逐步调大到“等待基本消失但 DB 仍稳定”的点。

    3)缓存:不是“全加 @Cacheable 就快”

      我也干过这种事:凡是查 DB 的方法都 @Cacheable。最后发现:

  • 热点小对象缓存后更慢(序列化/反序列化、锁、淘汰)
  • 数据频繁变更导致缓存抖动
  • 缓存穿透把 DB 又打穿

      缓存的本质是:用空间换时间,但前提是“命中”和“稳定”。

    我自己的规则(很朴素但好用)

      只缓存同时满足:

  • 获取/计算 > 50ms
  • 访问频繁
  • 变更不那么频繁
  • 能接受短暂不一致

      另外,别把“派生字段”单独再缓存一次,应该复用主对象缓存。

    4)JSON 序列化:你返回的是“实体”,输出的是“整个世界”

      把 JPA Entity 直接当 API Response,Jackson 往往会:

  • 序列化一堆你根本不想给前端的字段
  • 触发懒加载(又回到 N+1)
  • 返回体巨大,网络与序列化双杀

      解决方式:DTO。别偷懒。DTO 不只是性能,它还是 API 契约隔离。

      record UserSummaryDTO(Long id, String name) {}@GetMapping("/users/{id}/summary")UserSummaryDTO summary(@PathVariable Long id) { var u = userService.findById(id); return new UserSummaryDTO(u.getId(), u.getName());}5)异步:别让用户等“非关键路径”

      注册接口里最常见的慢操作:发邮件、打点、发 MQ、刷缓存……这些不影响“主流程成功”的动作,就不要阻塞响应。

      Spring 的 @Async + 明确线程池,是最简单的提速手段之一。

      @EnableAsync@Configurationclass AsyncConfig { @Bean("appExecutor") Executor appExecutor() { var ex = new ThreadPoolTaskExecutor(); ex.setCorePoolSize(5); ex.setMaxPoolSize(10); ex.setQueueCapacity(200); ex.setThreadNamePrefix("async-"); ex.initialize(); return ex; }}@Async("appExecutor")public void sendWelcomeEmail(...) { ... }

      额外一句:如果你在 Java 21 + Spring Boot 新版本上,虚拟线程也值得评估,但不要把它当“银弹”。异步边界、IO 依赖、DB 池大小依然决定上限。

    6)启动慢:不是“Spring 太重”,是你让它“全加载”

      项目一大,启动慢通常来自两件事:

  • 扫描范围太大
  • Bean 过早初始化(eager)三个立竿见影的动作

      A. 缩小扫描包

      @SpringBootApplication(scanBasePackages = { "com.xxx.controller", "com.xxx.service", "com.xxx.config"})class App {}

      B. 开启 Lazy Init(谨慎:首次调用会变慢)

      spring: main: lazy-initialization: true

      C. spring-context-indexer(大项目很香)它会在构建期预计算候选组件,减少运行期扫描与反射开销。

    进阶:GraalVM Native Image / CRaC(看你的部署形态)

      如果你是大量小服务、弹性伸缩、冷启动敏感:Native Image 的收益往往非常直观,但你要接受构建与兼容性成本。

    7)JVM:你以为是“内存泄漏”,其实是“GC 在求救”

      很多“内存问题”本质是:

  • heap 太小导致频繁 GC
  • 参数不匹配 Spring Boot 的分配特征
  • 线上默认值并不等于稳定值

      一个可用起点(示例):

      java -jar app.jar \ -Xms512m -Xmx2g \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:+UseStringDeduplication8)生产配置:最“便宜”的 25% 性能提升

      很多性能提升不是改代码,而是改几行配置:Tomcat 线程、Hibernate 批处理、日志级别。

      server: tomcat: threads: max: 200 min-spare: 10 connection-timeout: 20000 accept-count: 100spring: jpa: properties: hibernate: jdbc: batch_size: 20 order_inserts: true order_updates: true show-sql: falselogging: level: org.springframework: WARN org.hibernate: WARN com.yourapp: INFO最后一段我想强调的

      性能优化的核心不是“技巧”,而是“把默认值当成可质疑对象”。Spring Boot 帮你把项目跑起来,但“跑得快、跑得稳、跑得省”,得靠你理解它默认做了什么、什么时候不适合你、以及你用数据证明改变是有效的。

  • heap 太小导致频繁 GC
  • 参数不匹配 Spring Boot 的分配特征
  • 线上默认值并不等于稳定值

      一个可用起点(示例):

      java -jar app.jar \ -Xms512m -Xmx2g \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:+UseStringDeduplication8)生产配置:最“便宜”的 25% 性能提升

      很多性能提升不是改代码,而是改几行配置:Tomcat 线程、Hibernate 批处理、日志级别。

      server: tomcat: threads: max: 200 min-spare: 10 connection-timeout: 20000 accept-count: 100spring: jpa: properties: hibernate: jdbc: batch_size: 20 order_inserts: true order_updates: true show-sql: falselogging: level: org.springframework: WARN org.hibernate: WARN com.yourapp: INFO最后一段我想强调的

      性能优化的核心不是“技巧”,而是“把默认值当成可质疑对象”。Spring Boot 帮你把项目跑起来,但“跑得快、跑得稳、跑得省”,得靠你理解它默认做了什么、什么时候不适合你、以及你用数据证明改变是有效的。

       如果这篇内容对你有帮助,欢迎点赞 、收藏 、转发给需要的朋友

      我会持续分享:

  • Java 核心与高阶实战
  • AI / Agent / 前沿技术落地
  • 真实项目经验 & 架构思考
  • 企业数字化与产品实践

       关注我,一起把“技术”真正用在项目和业务里。

       你的每一次支持,都是我持续输出高质量内容的最大动力。

    本文标题:写了 10年 Java 才悟出来:Spring Boot “变快”的真相,不在代码本文网址:https://www.sz12333.net.cn/zhzx/kexue/69666.html 编辑:12333社保查询网
  • 本站是社保查询公益性网站链接,数据来自各地人力资源和社会保障局,具体内容以官网为准。
    定期更新查询链接数据 苏ICP备17010502号-11