Spring启动分析器
Spring启动分析器生成一个交互式的Spring应用启动报告,让你了解哪些因素影响了应用的启动时间,并帮助优化它。用户界面参考了spring-boot-startup-report。
🤩亮点
📈Spring启动分析报告
Spring Bean初始化详情支持初始化时间/bean名称搜索,Spring Bean初始化时间线,方法调用次数和时间统计(支持自定义方法),未使用的Jar包(帮助优化fat jar),以及应用启动线程墙钟分析,帮助开发人员快速分析和定位应用启动瓶颈。支持linux/mac/windows系统。
-
Spring Bean初始化详情
-
Spring Bean初始化时间线
-
方法调用次数和时间统计
-
未使用的Jar包
-
应用启动线程墙钟分析
🚀Spring启动优化
提供Spring Bean异步初始化jar包,对初始化时间较长的bean异步执行init
和@PostConstruct
方法,以提高应用启动速度。
🧭如何使用
📈Spring启动数据收集
安装
提供两种安装方式:手动安装和一键脚本安装。
1. 手动安装
-
点击realease下载最新版本的tar.gz包
-
创建新文件夹并解压文件
对于Linux/Mac系统,可以考虑使用以下命令:
mkdir -p ${HOME}/spring-startup-analyzer
cd 下载路径
tar -zxvf spring-startup-analyzer.tar.gz -C 你的安装路径/spring-startup-analyzer
2. Shell脚本安装(仅适用于Linux/Mac)
curl -sS https://raw.githubusercontent.com/linyimin0812/spring-startup-analyzer/main/bin/install.sh | sh
默认安装目录:$HOME/spring-startup-analyzer
配置
本项目提供几个配置选项,这些配置不是必须的,可以使用默认设置。
有两种配置方式:
- 直接在配置文件中配置:
你的安装路径/spring-startup-analyzer/config/spring-startup-analyzer.properties
- 通过启动参数配置,例如,设置应用启动健康检查超时时间为30分钟:
-Dspring-startup-analyzer.app.health.check.timeout=30
确定应用启动成功的标准如下:
- 对
SpringApplication.run
方法进行字节码增强,方法退出时认为应用启动完成(仅适用于Spring Boot应用)。 - 轮询健康检查请求URL,收到200响应时认为启动完成(适用于所有Spring应用)。
- 如果上述两种方法都不成功,超过应用启动健康检查超时时间后认为应用启动完成。
对于非Spring Boot应用,需要使用spring-startup-analyzer.app.health.check.endpoints
配置健康检查URL。
配置选项 | 描述 | 默认值 |
---|---|---|
spring-startup-analyzer.app.health.check.timeout | 应用启动检查超时时间(分钟) | 20 |
spring-startup-analyzer.app.health.check.endpoints | 应用启动成功检查URL,可配置多个URL,用逗号分隔 | http://127.0.0.1:7002/actuator/health |
spring-startup-analyzer.admin.http.server.port | 管理端口 | 8065 |
spring-startup-analyzer.async.profiler.sample.thread.names | Async Profiler收集的线程名称,支持多个配置,用逗号分隔 | main |
spring-startup-analyzer.async.profiler.interval.millis | async profiler采样间隔(毫秒) | 5 |
spring-startup-analyzer.linux.and.mac.profiler | 指定linux/mac火焰图分析器:async_profiler/jvm_profiler | jvm_profiler |
spring-startup-analyzer.log.path | 日志路径 - startup.log: 启动日志 - transform.log: 类重转换日志 | $HOME/spring-startup-analyzer/logs |
应用启动
本项目作为agent启动,因此您可以在启动命令中添加参数-javaagent:你的安装路径/spring-startup-analyzer/lib/spring-profiler-agent.jar
。
- 如果使用Java命令行启动应用,您需要在命令行中添加参数,例如:
java -javaagent:/Users/runner/spring-startup-analyzer/lib/spring-profiler-agent.jar \
-Dproject.name=mac-demo \
-Dspring-startup-analyzer.admin.http.server.port=8066 \
-jar /Users/runner/spring-startup-analyzer/spring-boot-demo.jar
- 如果要在IDEA中启动,您需要在VM选项中添加以下内容:
应用启动完成后,控制台和startup.log文件中会打印======= spring-startup-analyzer finished, click http://localhost:xxxx to visit details. ======
消息。您可以通过这个输出判断分析是否成功完成。
自定义扩展
如果您想自定义分析功能,需要将spring-profiler-starter
pom作为您扩展项目的父pom。然后,您可以使用项目暴露的接口进行扩展。更多详情,您可以参考spring-profiler-extension的实现。
<parent>
<groupId>io.github.linyimin0812</groupId>
<artifactId>spring-profiler-starter</artifactId>
<version>最新版本</version>
</parent>
扩展接口
io.github.linyimin0812.profiler.api.EventListener
public interface EventListener extends Startable {
/**
* 应用启动期间调用
*/
void start();
/**
* 应用启动完成后调用
*/
void stop();
/**
* 需要增强的类
* @param className
* @return true: 增强,false: 不增强
*/
boolean filter(String className);
/**
* 需要增强的方法(此方法依赖于filter(className)条件。只有当filter(className)返回true时才会执行)
* @param methodName
* @param methodTypes
* @return true: 增强,false: 不增强
*/
default boolean filter(String methodName, String[] methodTypes) {
return true;
}
/**
* 事件响应处理逻辑
* @param event 触发事件
*/
void onEvent(Event event);
/**
* 需要监听的事件
* @return 需要监听的事件
*/
List<Event.Type> listen();
}
start()
和stop()
方法代表系统的生命周期,分别在应用启动开始和完成时调用。filter()
方法指定需要增强的类/方法。listen()
方法指定需要监听的事件,包括方法进入
和方法返回
事件。onEvent()
方法在监听的事件发生时被调用。
例如,以下是一个扩展,用于统计应用启动过程中java.net.URLClassLoader.findResource(String)方法的调用次数:
FindResourceCounter 示例
@MetaInfServices
public class FindResourceCounter implements EventListener {
private final AtomicLong COUNT = new AtomicLong(0);
@Override
public boolean filter(String className) {
return "java.net.URLClassLoader".equals(className);
}
@Override
public boolean filter(String methodName, String[] methodTypes) {
if (!"findResource".equals(methodName)) {
return false;
}
return methodTypes != null && methodTypes.length == 1 && "java.lang.String".equals(methodTypes[0]);
}
@Override
public void onEvent(Event event) {
if (event instanceof AtEnterEvent) {
// 进入findResource方法
} else if (event instanceof AtExitEvent) {
// findResource返回
}
// 调用次数计数
COUNT.incrementAndGet();
}
@Override
public List<Event.Type> listen() {
return Arrays.asList(Event.Type.AT_ENTER, Event.Type.AT_EXIT);
}
@Override
public void start() {
System.out.println("============== 我的扩展开始 =============");
}
@Override
public void stop() {
System.out.println("============== 我的扩展结束 =============");
System.out.println("findResource 调用次数: " + COUNT.get());
}
}
打包和运行
spring-profiler-starter
的 pom 文件已经定义了一个打包插件,默认情况下会将生成的 JAR 文件复制到 $HOME/spring-startup-analyzer/extension
目录。
mvn clean package
按照安装部分的步骤安装完这个项目后,你可以执行上述打包命令。打包完成后,你可以按照应用启动部分的描述启动应用程序来加载扩展 JAR 文件。
🚀Spring 启动优化
生产环境启动时间优化
从应用启动数据收集部分,你可以获取初始化时间较长的 Bean。由于 Spring 启动过程是单线程的,为了优化应用启动时间,你可以考虑将这些耗时 Bean 的初始化方法异步化。
注意:
- 建议优先优化 Bean 的代码,从根本上解决初始化时间长的问题
- 对于二方包或三方包中初始化时间较长的 Bean(无法优化代码),考虑对这些 Bean 进行异步初始化
- 对于不依赖其他 Bean 的 Bean,可以放心地进行异步初始化,你可以通过查看Bean 加载时间部分的
Root Bean
来确定一个 Bean 是否依赖其他 Bean - 对于依赖其他 Bean 的 Bean,需要仔细分析。它们在应用启动过程中不应被其他 Bean 调用,否则可能会导致问题
支持异步的 Bean 类型
支持通过 @Bean、@PostConstruct 和 @ImportResource 初始化的 bean。示例:spring-boot-async-bean-demo
- 使用
@Bean(initMethod = "init")
注解的 Bean
@Bean(initMethod = "init")
public TestBean testBean() {
return new TestBean();
}
- 使用
@PostConstruct
注解的 Bean
@Component
public class TestComponent {
@PostConstruct
public void init() throws InterruptedException {
Thread.sleep(20 * 1000);
}
}
使用方法
- 导入依赖
<dependency>
<groupId>io.github.linyimin0812</groupId>
<artifactId>spring-async-bean-starter</artifactId>
<version>${最新版本}</version>
</dependency>
- 配置
# 异步 Bean 可能会在 Spring Bean 初始化顺序的末尾,这可能导致异步优化效果不佳。开启此配置以优先加载异步 Bean。
spring-startup-analyzer.boost.spring.async.bean-priority-load-enable=true
# 需要异步初始化的 Bean 名称
spring-startup-analyzer.boost.spring.async.bean-names=testBean,testComponent
# 初始化 Bean 的线程池核心大小
spring-startup-analyzer.boost.spring.async.init-bean-thread-pool-core-size=8
# 初始化 Bean 的线程池最大大小
spring-startup-analyzer.boost.spring.async.init-bean-thread-pool-max-size=8
- 检查 Bean 是否异步初始化
在 $HOME/spring-startup-analyzer/logs/async-init-bean.log
文件中查看日志。对于异步初始化的方法,将以以下格式写入一条日志:
async-init-bean, beanName: ${beanName}, async init method: ${initMethodName}
日常和预发环境启动时间优化
为了优化日常和预发环境的启动时间,我们可以考虑热插拔。该项目提供了一个命令行工具来实现修改代码的热插拔。
- 从 release 下载
spring-startup-cli
- 在项目工作目录下执行命令行工具
java -jar spring-startup-cli.jar
- 使用
config
命令配置信息
config set
- 执行
reload
命令