Spring Boot Rest API生产环境响应过慢,运行1-2天后性能下降
这种运行一段时间后性能衰减、重启就恢复的情况,在Spring Boot + AWS EC2的生产环境里真的挺常见的,我帮你梳理几个最可能的原因,你可以逐一排查:
这绝对是头号嫌疑犯。如果你的Spring Boot应用存在内存泄漏——比如静态集合一直持有对象引用、未关闭的IO流/网络连接、第三方库暗藏内存泄漏——运行1-2天后堆内存会被慢慢占满,GC会频繁触发,甚至出现OOM前的严重卡顿,直接导致响应变慢。
排查实操:
- 用
jstat -gc <你的应用PID>实时监控GC状态,观察Young GC和Full GC的频率、耗时变化,如果Full GC越来越频繁、耗时越来越长,基本实锤内存问题; - 定期导出堆快照:
jmap -dump:format=b,file=heap.hprof <你的应用PID>,然后用VisualVM或MAT工具分析,找出哪些对象在持续占用内存不释放; - 检查JVM启动参数,比如是否给了合理的
-Xmx/-Xms,有没有开启GC日志(配置-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log),通过日志分析GC的异常波动。
很多Spring Boot应用用HikariCP或Druid做连接池,如果代码里没正确释放连接——比如try-with-resources没用好,或者异常分支里连接没关闭——运行一段时间后连接池会被占满,新请求拿不到连接就会阻塞,自然响应变慢。
排查实操:
- 查看连接池监控指标,比如HikariCP的
hikari.pool.totalConnections和hikari.pool.idleConnections,如果空闲连接持续减少、总连接数达到最大值,说明有连接泄漏; - 扫一遍数据库操作的代码,确保所有
Connection、Statement、ResultSet都用try-with-resources包裹,自动释放资源; - 配置连接池的超时回收机制,比如HikariCP的
connectionTimeout(连接超时)、idleTimeout(空闲连接回收),避免无效连接占着资源不释放。
除了数据库连接,还有其他资源可能悄悄泄漏:比如Redis连接、文件流、网络Socket、线程池线程等。比如创建了线程但没正确关闭线程池,或者WebSocket连接没断开导致线程一直占用,都会慢慢耗尽EC2的CPU、内存或文件句柄。
排查实操:
- 用
top或htop监控EC2的CPU、内存、线程数,如果线程数持续增长,大概率是线程泄漏; - 检查代码里的资源操作:文件读写后有没有关闭流?RedisTemplate的操作有没有异常未处理导致连接没归还?
- 用
lsof -p <你的应用PID> | wc -l查看文件句柄数,如果持续增长,说明有文件句柄泄漏。
如果你的应用日志输出太多,又没配置日志滚动(比如Logback没设maxFileSize或maxHistory),运行几天后日志文件会占满EC2的磁盘空间,导致系统IO飙升,甚至应用无法写入日志而卡顿。
排查实操:
- 用
df -h查看EC2磁盘使用情况,看是否有分区使用率接近100%; - 检查日志配置文件,确保开启了日志滚动和过期删除,比如Logback里配置
<rollingPolicy>,限制单文件大小和保留天数; - 生产环境别用debug级别日志,改成info或warn,减少日志输出量。
AWS的共享实例(比如t2、t3系列)有CPU积分机制,如果应用持续高负载,积分耗尽后CPU性能会被强制限制,响应自然变慢;另外如果实例所在的宿主机资源紧张,也会出现性能波动。
排查实操:
- 登录AWS控制台看EC2的监控指标:CPU使用率、磁盘IO、网络流量,如果CPU使用率突然下降但请求响应变慢,大概率是CPU积分耗尽;
- 如果是共享实例,要么换成固定性能的m系列实例,要么开启CPU积分无限模式;
- 用
free -m查看内存和swap使用情况,如果swap频繁被使用,说明系统内存不足,得升级实例配置。
如果应用用了Redis或本地缓存,缓存集中过期后大量请求直接打到数据库,导致数据库压力骤增,进而拖慢整个应用;或者本地缓存(比如Guava Cache)设置不合理,缓存对象太多占用内存。
排查实操:
- 查看数据库的慢查询日志,看是否有大量重复查询在某个时间点集中出现(对应缓存过期时间);
- 检查缓存过期时间设置,避免所有缓存都在同一时间过期(比如给过期时间加随机偏移);
- 监控缓存命中率,如果命中率持续下降,说明缓存策略有问题,得调整缓存键或过期时间。
如果应用依赖了外部服务(比如API网关、短信服务、OSS等),这些服务运行一段时间后性能下降或不稳定,会导致应用调用它们的接口超时,进而拖慢整个请求响应。
排查实操:
- 查看应用的错误日志,看是否有大量调用第三方服务的超时异常;
- 联系第三方服务提供商确认其服务状态,或者在代码里加超时重试和降级机制,别让依赖服务拖垮自己的应用。
如果应用中有定时任务(比如@Scheduled注解的任务),任务逻辑有问题——比如死循环、处理的数据量越来越大——运行几天后后台线程会占用大量CPU或内存,导致主线程资源不足。
排查实操:
- 查看定时任务的日志,看是否有任务执行时间越来越长的情况;
- 用
jstack <你的应用PID>导出线程栈,看看有没有线程一直处于RUNNABLE状态且占用大量CPU,或者处于BLOCKED状态等待资源。
内容的提问来源于stack exchange,提问作者Muhammad Ahsan

