# 如何提高 springboot 启动速度
# 启动时间分析
IDEA 自带集成了 async-profile 工具,所以我们可以通过火焰图来更直观的看到一些启动过程中的问题,比如下图例子当中,通过火焰图来看大量的耗时在 Bean 加载和初始化当中
TIP
IDEA 自带集成的 async-profile 工具,可在 Preferences 中搜索 Java Profiler 自定义配置,启动使用 Run with xx Profiler。
- y 轴表示调用栈,每一层都是一个函数,调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
- x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。
# 启动优化
# 减少业务初始化
大部分的耗时应该都在业务太大或者包含大量的初始化逻辑,比如建立数据库连接、Redis 连接、各种连接池等等,对于业务方的建议则是尽量减少不必要的依赖,能异步则异步。
# 延迟初始化
Spring Boot 2.2 版本后引入 spring.main.lazy-initialization属性,配置为 true 表示所有 Bean 都将延迟初始化。
可以一定程度上提高启动速度,但是第一次访问可能较慢。
spring.main.lazy - initialization = true;
# Spring Context Indexer
Spring5 之后版本提供了spring-context-indexer功能,主要作用是解决在类扫描的时候避免类过多导致的扫描速度过慢的问题。
使用方法也很简单,导入依赖,然后在启动类打上@Indexed注解,这样在程序编译打包之后会生成META-INT/spring.components文件,当执行 ComponentScan 扫描类时,会读取索引文件,提高扫描速度。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>
2
3
4
5
# 关闭 JMX
Spring Boot 2.2.X 版本以下默认会开启 JMX,可以使用 jconsole 查看,对于我们无需这些监控的话可以手动关闭它。
spring.jmx.enabled = false;
# 关闭分层编译
Java8 之后的版本,默认打开多层编译,使用命令 java -XX:+PrintFlagsFinal -version | grep CompileThreshold 查看。
Tier3 就是 C1、Tier4 就是 C2,表示一个方法解释编译 2000 次进行 C1 编译,C1 编译后执行 15000 次会进行 C2 编译。
我们可以通过命令使用 C1 编译器,这样就不存在 C2 的优化阶段,能够提高启动速度,同时配合 -Xverify:none/ -noverify 关闭字节码验证,但是,尽量不要在线上环境使用。
-XX:TieredStopAtLevel=1 -noverify
# JAR Index
Jar 包其实本质上就是一个 ZIP 文件,当加载类的时候,我们通过类加载器去遍历 Jar 包,找到对应的 class 文件进行加载,然后验证、准备、解析、初始化、实例化对象。
假设我们要在 A\B\C 3 个 Jar 包中查找一个 class,如果能够通过类型 com.C,立刻推断出具体在哪个 jar 包,就可以避免遍历 jar 的过程。
A.jarcom/AB.jarcom/BC.jarcom/C
通过 Jar Index 技术,就可以生成对应的索引文件 INDEX.LIST。
com/A --> A.jarcom/B --> B.jarcom/C --> C.jar
不过对于现在的项目来说,Jar Index 很难应用
# Heap Archive
JDK9 中引入了 HeapArchive,并且 JDK12 中被正式使用,我们可以认为 Heap Archive 是对 APPCDS 的一个延伸。
APPCDS 是持久化了类加载过程中验证、解析产生的数据,Heap Archive 则是类初始化(执行 static 代码块 cinit 进行初始化) 相关的堆内存的数据。
简单来讲,可以认为 HeapArchive 是在类初始化的时候通过内存映射持久化了一些 static 字段,避免调用类初始化器,提前拿到初始化好的类,提高启动速度。