概叙
实战:Java web应用性能分析之【Arthas性能分析trace监控后端性能】-CSDN博客
科普文:Java基础系列之后端性能优化汇总_java bex接口性能-CSDN博客
实战:Java基础系列性能优化之API接口超时排查【二】_java 日志中如何判断接口超时中断-CSDN博客
Java web应用性能分析之【java进程问题分析工具】_java性能分析-CSDN博客
Java web应用性能分析之【java进程问题分析概叙】_java进程性能监控web页面-CSDN博客
科普文:Java基础系列之后端性能优化汇总_java bex接口性能-CSDN博客
如题:为什么springboot的restful接口第一次访问很慢,第二次之后就快很多?
第一次访问
2025-04-23 20:06:10.072 |834f571028a64bb3b4f474b27b8b2329 |XNIO-1 task-1 | com.zxx.study.boot.web.config.ControllerAspect#logTime:39 | RestController方法 runAsync1 执行耗时: 329ms
2025-04-23 20:06:10.227 |834f571028a64bb3b4f474b27b8b2329 |XNIO-1 task-1 | com.zxx.study.common.web.interceptor.LoggingInterceptor#afterCompletion:126 | [/Ecommerce/api/v1/lock/runAsync1-响应] : GET: application/json: status: 200: costTime: 528ms
2025-04-23 20:06:10.387 |834f571028a64bb3b4f474b27b8b2329 |XNIO-1 task-1 | com.zxx.study.common.web.filter.TraceIdFilter#doFilter:111 | [0:0:0:0:0:0:0:1:/Ecommerce/api/v1/lock/runAsync1] : GET: null: status: 200: costTime: 763ms
第二次访问
2025-04-23 20:08:10.078 |438aa3bb4a434644b0b49b151ca684be |XNIO-1 task-1 | com.zxx.study.boot.web.config.ControllerAspect#logTime:35 | RestController方法 runAsync1 执行耗时: 6ms
2025-04-23 20:08:10.082 |438aa3bb4a434644b0b49b151ca684be |XNIO-1 task-1 | com.zxx.study.common.web.interceptor.LoggingInterceptor#afterCompletion:122 | [/Ecommerce/api/v1/lock/runAsync1-响应] : GET: application/json: status: 200: costTime: 11ms
2025-04-23 20:08:10.133 |438aa3bb4a434644b0b49b151ca684be |XNIO-1 task-1 | com.zxx.study.common.web.filter.TraceIdFilter#doFilter:111 | [0:0:0:0:0:0:0:1:/Ecommerce/api/v1/lock/runAsync1] : GET: null: status: 200: costTime: 70ms
如上日志所示:
第一次访问耗时【763ms【528ms【329ms】】】
第二次访问耗时【70ms【11ms【6ms】】】
访问controller,第一次和第二次访问相差10-50倍,是哪里出了问题?
当前环境:springboot2.6.8 + JDK11.0.2 同时用undertow替换了内置Tomcat。
这里面的原因很多,第一次访问慢通常涉及JVM的类加载和JIT编译、数据库连接池初始化、DispatcherServlet的懒加载、安全随机数生成器的问题,以及资源懒加载和缓存机制等等。
例如:
- Filter层耗时:TraceIdFilter本身可能处理了一些逻辑,比如生成Trace ID、记录请求开始时间等。如果Filter中有复杂的逻辑或阻塞操作,可能会导致耗时增加。需要检查Filter的实现,是否有耗时的代码。
- 序列化与反序列化:Controller处理的是POST请求,数据格式为application/json。如果请求体较大,JSON的解析和序列化可能消耗较多时间,尤其是在处理复杂或大数据量的情况下。可以通过优化DTO结构或使用更高效的序列化库(如Fastjson或Jackson的优化配置)来减少时间。
- 连接池初始化与资源获取:应用可能在处理请求时需要从数据库连接池获取连接,如果连接池初始化较慢或连接获取有延迟,会导致额外耗时。预热连接池可以避免首次获取连接的延迟,这在之前的回答中也有提到。
- 事务管理:如果Service层使用了声明式事务(如@Transactional),事务的提交或回滚可能会在切面之外消耗时间。特别是如果事务范围较大,包含了一些非数据库操作,会导致事务提交时间较长。需要检查事务的边界和配置,确保事务范围最小化。
- 数据库连接池未预热:首次请求需要建立连接,之后复用。
- JVM JIT编译:首次执行时方法被解释执行,之后编译优化。
- 缓存未预热:首次查询未命中缓存,后续命中。
- 类加载延迟:首次加载类需要时间。
- 网络与IO延迟:虽然请求是本地测试(127.0.0.1),但如果有代理、网关或负载均衡器,也可能引入延迟。此外,日志记录、监控上报等异步操作如果同步执行,也会增加耗时。需要确认是否有网络层面的中间件或同步日志操作。
- 第三方拦截器或组件:除了用户提到的切面,可能还有其他全局拦截器、安全框架(如Spring Security)、权限校验等组件在处理请求,这些组件的处理时间可能未被切面捕获,从而导致总耗时增加。需要审查所有全局配置和依赖库的拦截逻辑。
关键在于:
- 检查并优化JVM配置:调整堆内存和垃圾回收策略,提升性能。
- 监控和分析类加载:通过日志和工具,了解哪些类在首次请求时被加载,优化类加载过程。
- 优化数据库连接池配置:确保连接池在应用启动时就初始化,减少首次请求时的连接创建延迟。
- 启用合理的缓存策略:利用方法级缓存、HTTP缓存等,减少重复计算和资源访问。
- 预加载关键组件和数据:在应用启动时,提前加载所需的组件和数据,避免首次请求时的延迟。
第一次请求延迟的主要原因分析和解决方案
1. JVM 类加载与JIT编译预热
- 首次请求:触发类加载(如控制器、过滤器、Spring Bean等),且字节码由解释器逐行执行,未触发JIT编译优化。
- 后续请求:热点代码(如接口处理逻辑)被JIT编译为本地机器码,执行效率提升。
解决方案:
1.1 JVM 预热与参数优化
# 启用并行类加载
-XX:+AlwaysPreTouch
# 降低JIT编译阈值
-XX:TieredStopAtLevel=1 -XX:CompileThreshold=500
1.2 查看JVM类加载日志
在JVM启动参数中添加-verbose:class
,查看类加载的时间,识别是否有特定类加载导致延迟:java -verbose:class -jar yourapp.jar
1.3 代码预热
@Bean
public CommandLineRunner preheat(WebApplicationContext context) {
return args -> {
new RestTemplate().getForObject("http://localhost:8080/api/v1/lock/runAsync1", String.class);
};
}
2. 数据库连接池和线程池初始化
- 首次请求:若涉及数据库操作,连接池(如HikariCP)需首次创建物理连接(TCP握手、权限验证),耗时较高。
- 后续请求:直接复用连接池缓存连接,无初始化开销。
解决方案
2.1 初始化线程池
2.2 初始化数据库连接池
2.3数据库预热
预热的日志
3. Tomcat与DispatcherServlet延迟加载
- 默认行为:Spring Boot内嵌Tomcat的
DispatcherServlet
可能设置为懒加载,首次请求需初始化Servlet及关联组件(如消息转换器、拦截器)。 - 后续请求:组件已初始化完成,直接处理请求。
解决方案
3.1undertow替换tomcat
科普文:SpringBoot项目禁用Tomcat,并用Undertow替换_springboot tomcat 禁用jsp-CSDN博客
科普文:Java web应用性能分析之【Spring Boot内嵌服务器选型:Tomcat vs Jetty vs Undertow 】_内置undertow支持并发-CSDN博客
3.2 显式配置:实现 DispatcherServlet 的启动时加载
@Configuration
@Slf4j
public class SystemConfig {
/**
*
* 在 Undertow 中需通过 编程显式配置 实现 DispatcherServlet 的启动时加载,
* 而 spring.mvc.servlet.load-on-startup=1 仅适用于 Tomcat 环境。
*
* 若需在 Undertow 中实现 启动时初始化 DispatcherServlet,需通过编程方式显式配置:
* 自定义 ServletRegistrationBean
* */
/**
* 显式注册 DispatcherServlet,并强制关联 WebApplicationContext
*/
@Bean
public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
WebApplicationContext webApplicationContext) {
// 手动创建 DispatcherServlet 并注入 WebApplicationContext
DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);
ServletRegistrationBean<DispatcherServlet> registration =
new ServletRegistrationBean<>(dispatcherServlet, "/");
// 关键:设置 Bean 名称与默认值一致,避免冲突
registration.setName("dispatcherServlet");
// 强制启动时初始化
registration.setLoadOnStartup(1);
if (log.isDebugEnabled()) {
log.debug("Undertow:强制启动时加载 DispatcherServlet.");
}
return registration;
}
/**
* 显式提供 DispatcherServletPath Bean,解决 ErrorMvcAutoConfiguration 依赖问题
*/
@Bean
public DispatcherServletPath dispatcherServletPath() {
return () -> "/"; // 路径需与 ServletRegistrationBean 的映射路径一致
}
}
4. 资源懒加载与缓存机制
- 首次请求:
- ORM框架(如Hibernate)加载实体元数据并生成SQL模板。
- MyBatis解析Mapper XML文件或注解。
- 后续请求:资源已缓存,直接复用。
@GetMapping("/data")
@Cacheable(value = "responseDataCache", key = "#id")
public ResponseEntity<?> getData(@PathVariable Long id) {
// 具体实现
}
5. 打印日志的影响
实战:Java web应用性能分析之【spring boot 高性能日志:UDP+异步日志log4j2+logstash】-CSDN博客
实战:Java web应用性能分析之【Arthas性能分析trace监控后端性能】-CSDN博客
6.过滤器、拦截器、AOP切面的影响
在Spring Boot应用程序中,使用过滤器(Filters)、拦截器(Interceptors)和AOP(Aspect-Oriented Programming)是常见的实践,用于处理横切关注点,如日志记录、安全检查、事务管理等。这些技术确实可以增强应用的功能性和可维护性,但同时也可能会对性能产生影响,尤其是在高负载或高频请求的场景下。
下面我们来分析这些技术对RESTful接口性能的可能影响:
1. 过滤器(Filters)
性能影响:
-
正向过滤器:通常在请求到达控制器之前执行,例如用于身份验证、请求日志记录等。如果过滤器执行的计算密集型操作或阻塞操作(如远程调用),则可能会显著增加延迟。
-
反向过滤器:在响应发送给客户端之前执行,主要用于修改响应内容或添加额外的响应头。这类操作通常对性能影响较小。
建议:
-
确保过滤器执行的操作尽可能轻量级。
-
避免在过滤器中进行复杂的业务逻辑处理或耗时的操作。
-
使用异步处理或非阻塞IO操作来优化性能。
2. 拦截器(Interceptors)
性能影响:
-
拦截器通常在控制器方法执行前后执行,用于拦截请求和响应。与过滤器类似,如果拦截器中包含耗时的操作,将直接增加处理时间。
建议:
-
保持拦截器逻辑的简洁性,避免在其中执行复杂的业务逻辑。
-
对于需要耗时处理的操作,考虑异步处理或使用其他机制(如消息队列)。
3. AOP(面向切面编程)
性能影响:
-
AOP通过在方法执行前后加入额外的逻辑(如日志记录、事务管理等)来实现横切关注点的统一处理。如果AOP的切面过于复杂或在关键路径上执行,会影响性能。
建议:
-
优化AOP切面的使用,避免在关键路径上使用AOP进行耗时操作。
-
使用@Async注解进行异步处理,减少对主线程的影响。
-
考虑使用更细粒度的控制,例如使用
@Around
注解时,精确控制哪些方法需要被增强。
4.优化策略:
-
异步处理:对于可能阻塞的长时间操作,使用
@Async
注解实现异步处理。 -
缓存:对于重复计算或不常变化的数据,使用缓存减少计算和数据库访问。
-
最小化切面范围:仅对必要的类和方法应用AOP增强,避免过度使用。
-
性能测试:定期进行性能测试,评估不同配置下的响应时间和吞吐量,找出瓶颈并进行优化。
-
监控和分析:使用工具如Spring Boot Actuator、Micrometer等进行监控和分析,了解哪些部分是性能瓶颈。
通过上述策略,可以在保证应用功能的同时,有效控制这些技术对RESTful接口性能的影响。