SpringBoot啟動(dòng)慢的優(yōu)化指南
摘要
本摘要基于視頻畫面字幕與關(guān)鍵幀整理。視頻主題是 Spring Boot 應(yīng)用啟動(dòng)耗時(shí)優(yōu)化,核心觀點(diǎn)是:不要一上來盲目“加內(nèi)存、換 SSD、改啟動(dòng)參數(shù)”,而是先用工具定位啟動(dòng)鏈路中真正耗時(shí)的階段,再針對(duì)性優(yōu)化。
1. 視頻核心結(jié)論
這個(gè)視頻講的是一個(gè)典型面試題/項(xiàng)目?jī)?yōu)化題:
Spring Boot 應(yīng)用啟動(dòng)很慢,比如 90 秒,如何優(yōu)化到 15 秒以內(nèi)?
視頻給出的答案不是直接羅列優(yōu)化項(xiàng),而是強(qiáng)調(diào):
- 先診斷,再優(yōu)化:先確認(rèn)慢在哪里,是 Bean 初始化慢、數(shù)據(jù)源連接慢,還是啟動(dòng)后置任務(wù)阻塞。
- 用啟動(dòng)耗時(shí)數(shù)據(jù)說話:通過 Spring Boot Actuator 的
/actuator/startup或 APM 查看各階段耗時(shí)。 - 針對(duì)大頭優(yōu)化:重點(diǎn)處理 Bean 掃描/初始化、數(shù)據(jù)源連接池初始化、ApplicationRunner/監(jiān)聽器同步任務(wù)。
- 最終優(yōu)化鏈路:案例中啟動(dòng)時(shí)間大致從 78 秒 → 55 秒 → 28 秒 → 18 秒 → 12 秒,達(dá)到 15 秒以內(nèi)。
2. 面試回答思路
錯(cuò)誤回答示例
視頻開頭提到,一些候選人會(huì)直接回答:
- 加內(nèi)存
- 換 SSD
- 減少啟動(dòng)參數(shù)
- 減少一些
@Component掃描
這些回答不是完全錯(cuò),但問題在于:沒有先定位瓶頸,屬于拍腦袋優(yōu)化。
推薦回答框架
更好的回答應(yīng)該是:
我不會(huì)直接說改哪個(gè)配置,而是先接入啟動(dòng)耗時(shí)分析,比如 Spring Boot Actuator 的
/actuator/startup或 APM,確認(rèn)啟動(dòng)慢到底慢在 Bean 初始化、數(shù)據(jù)源初始化、配置加載,還是自定義 Runner/Listener。然后針對(duì)耗時(shí)最大的階段逐項(xiàng)優(yōu)化。
3. 啟動(dòng)耗時(shí)診斷方式
視頻建議使用:
Spring Boot Actuator /actuator/startup
它可以看到 Spring Boot 啟動(dòng)過程中各階段的耗時(shí),幫助判斷瓶頸。
視頻案例中看到的耗時(shí)大致是:
| 階段 | 耗時(shí) | 問題說明 |
|---|---|---|
| Bean 初始化 | 52 秒 | Bean 數(shù)量太多,啟動(dòng)時(shí)全部實(shí)例化 |
| 數(shù)據(jù)源連接 | 18 秒 | 連接池啟動(dòng)時(shí)預(yù)創(chuàng)建數(shù)據(jù)庫(kù)連接 |
| 自定義 ApplicationRunner | 6 秒 | 啟動(dòng)時(shí)同步加載配置文件,阻塞主線程 |
| 其他 | 2 秒 | 其他啟動(dòng)開銷 |
這個(gè)數(shù)據(jù)說明:Bean 初始化是最大頭,占了絕大部分時(shí)間。
4. 第一類優(yōu)化:減少不必要的 Bean 掃描與初始化
問題原因
Spring Boot 啟動(dòng)時(shí)會(huì)掃描并初始化大量組件,例如:
@Component@Service@Configuration@Repository
項(xiàng)目越大、依賴包越多、自動(dòng)配置越多,啟動(dòng)時(shí)需要實(shí)例化的 Bean 就越多。
視頻中的例子是:
當(dāng)前項(xiàng)目是訂單服務(wù),但
pom中引入了用戶服務(wù) SDK,SDK 里帶了一堆@Configuration,訂單服務(wù)根本用不到這些 Bean,但啟動(dòng)時(shí)仍然會(huì)初始化。
優(yōu)化方式
可以通過 @ComponentScan 的 excludeFilters 排除無關(guān)包或無關(guān)配置。
示例:
@SpringBootApplication
@ComponentScan(
basePackages = "com.example",
excludeFilters = {
@ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "com.example.user.sdk.*"
)
}
)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
視頻中的優(yōu)化效果
| 優(yōu)化前 | 優(yōu)化后 |
|---|---|
| Bean 數(shù)量約 1200 個(gè) | Bean 數(shù)量約 800 個(gè) |
| 啟動(dòng)時(shí)間 78 秒 | 啟動(dòng)時(shí)間 55 秒 |
注意點(diǎn)
排除 Bean 不能盲目操作,需要確認(rèn):
- 被排除的包確實(shí)不是當(dāng)前服務(wù)必須依賴;
- 不會(huì)誤刪核心
Configuration; - 測(cè)試環(huán)境和預(yù)發(fā)環(huán)境需要完整回歸;
- 對(duì)公共 SDK 最好拆分 starter 或控制自動(dòng)配置條件,而不是所有服務(wù)無差別加載。
5. 第二類優(yōu)化:開啟 Bean 懶加載
核心配置
spring.main.lazy-initialization=true
原理
開啟懶加載后,Bean 不再全部在應(yīng)用啟動(dòng)階段創(chuàng)建,而是在第一次真正使用時(shí)才創(chuàng)建。
視頻中的類比是:
餐廳開張時(shí),不是一開門就把 100 道菜全部做好,而是客人點(diǎn)什么再做什么。
視頻中的優(yōu)化效果
開啟懶加載后,大量非啟動(dòng)必要 Bean 被推遲初始化:
| 階段 | 啟動(dòng)時(shí)間 |
|---|---|
| 排除無關(guān) Bean 后 | 55 秒 |
| 開啟懶加載后 | 28 秒 |
重要風(fēng)險(xiǎn):循環(huán)依賴可能延遲暴露
視頻特別強(qiáng)調(diào)了懶加載的坑:
如果 Bean 之間存在循環(huán)依賴,懶加載可能不會(huì)在啟動(dòng)時(shí)報(bào)錯(cuò),而是在某個(gè)接口第一次被調(diào)用時(shí)才報(bào)錯(cuò),這比啟動(dòng)時(shí)報(bào)錯(cuò)更危險(xiǎn)。
原因是:
- 正常啟動(dòng)時(shí),Spring 會(huì)在 Bean 創(chuàng)建過程中發(fā)現(xiàn)循環(huán)依賴;
- 懶加載模式下,很多 Bean 啟動(dòng)時(shí)沒有創(chuàng)建;
- 依賴問題可能被推遲到運(yùn)行時(shí)才暴露。
正確做法
- 先關(guān)閉懶加載;
- 正常啟動(dòng)一次;
- 檢查日志里有沒有循環(huán)依賴告警;
- 有問題先解決循環(huán)依賴;
- 再開啟懶加載。
6. 第三類優(yōu)化:調(diào)整 HikariCP 連接池初始化
問題原因
視頻中提到:
HikariCP 默認(rèn)啟動(dòng)時(shí)就會(huì)創(chuàng)建連接池,比如
minimum-idle默認(rèn)值為 10,意味著啟動(dòng)階段立刻創(chuàng)建 10 個(gè)數(shù)據(jù)庫(kù)連接。如果數(shù)據(jù)庫(kù)響應(yīng)慢,比如跨機(jī)房、網(wǎng)絡(luò)抖動(dòng),這一步就可能拖慢幾秒甚至十幾秒。
優(yōu)化配置
spring.datasource.hikari.minimum-idle=0
含義是:
啟動(dòng)時(shí)不提前創(chuàng)建空閑連接,第一個(gè)請(qǐng)求來了再按需創(chuàng)建連接。
視頻中的優(yōu)化效果
| 階段 | 啟動(dòng)時(shí)間 |
|---|---|
| 開啟懶加載后 | 28 秒 |
| 調(diào)整 HikariCP 后 | 18 秒 |
注意點(diǎn)
這個(gè)配置適合啟動(dòng)速度敏感的服務(wù),但需要結(jié)合業(yè)務(wù)判斷:
- 如果服務(wù)剛啟動(dòng)就有高并發(fā)流量,完全不預(yù)熱連接可能導(dǎo)致首批請(qǐng)求略慢;
- 可以結(jié)合預(yù)熱接口、灰度發(fā)布、就緒探針來平衡啟動(dòng)速度和首個(gè)請(qǐng)求耗時(shí);
- 如果數(shù)據(jù)庫(kù)連接慢的根因是網(wǎng)絡(luò)、DNS、跨機(jī)房,也應(yīng)該同步排查基礎(chǔ)設(shè)施問題。
7. 第四類優(yōu)化:ApplicationRunner / 啟動(dòng)監(jiān)聽器異步化
問題原因
視頻中最后發(fā)現(xiàn):
有一個(gè)
ApplicationRunner在啟動(dòng)時(shí)加載本地配置文件,執(zhí)行讀文件、解析、校驗(yàn)等操作,耗時(shí) 6 秒。但這份配置不是接口啟動(dòng)必須依賴的。
如果這些邏輯寫在:
ApplicationRunnerCommandLineRunnerApplicationListener@PostConstruct
并且同步執(zhí)行,就會(huì)阻塞 Spring Boot 主啟動(dòng)線程。
優(yōu)化方式
將非核心路徑的啟動(dòng)任務(wù)改成異步執(zhí)行。
示例:
@EnableAsync
@Configuration
public class AsyncConfig {
}
@Component
public class LocalConfigWarmupRunner implements ApplicationRunner {
private final LocalConfigService localConfigService;
public LocalConfigWarmupRunner(LocalConfigService localConfigService) {
this.localConfigService = localConfigService;
}
@Async
@Override
public void run(ApplicationArguments args) {
localConfigService.loadAndValidate();
}
}
更穩(wěn)妥的企業(yè)級(jí)做法是使用獨(dú)立線程池:
@Configuration
@EnableAsync
public class AsyncExecutorConfig {
@Bean("startupTaskExecutor")
public Executor startupTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("startup-task-");
executor.initialize();
return executor;
}
}
@Async("startupTaskExecutor")
public void loadAndValidate() {
// 讀取文件、解析、校驗(yàn)、緩存預(yù)熱等非核心啟動(dòng)任務(wù)
}
視頻中的優(yōu)化效果
| 階段 | 啟動(dòng)時(shí)間 |
|---|---|
| 調(diào)整數(shù)據(jù)源后 | 18 秒 |
| Runner 異步化后 | 12 秒 |
8. 完整優(yōu)化鏈路復(fù)盤
視頻中的完整優(yōu)化鏈路可以總結(jié)為:
| 步驟 | 優(yōu)化動(dòng)作 | 啟動(dòng)耗時(shí)變化 |
|---|---|---|
| 0 | 初始狀態(tài),存在大量 Bean、數(shù)據(jù)源預(yù)連接、Runner 同步阻塞 | 78 秒 |
| 1 | 排除無關(guān) SDK / 無關(guān)配置類,減少 Bean 數(shù)量 | 78 秒 → 55 秒 |
| 2 | 開啟 spring.main.lazy-initialization=true | 55 秒 → 28 秒 |
| 3 | 設(shè)置 spring.datasource.hikari.minimum-idle=0 | 28 秒 → 18 秒 |
| 4 | 將非必要 ApplicationRunner 改為異步執(zhí)行 | 18 秒 → 12 秒 |
最終啟動(dòng)耗時(shí)進(jìn)入 15 秒以內(nèi)。
9. 可以沉淀成企業(yè)級(jí)啟動(dòng)優(yōu)化規(guī)范
9.1 啟動(dòng)耗時(shí)必須可觀測(cè)
建議接入:
- Spring Boot Actuator
/actuator/startup - APM 啟動(dòng)鏈路追蹤
- 應(yīng)用啟動(dòng)日志分段耗時(shí)打印
- Bean 數(shù)量統(tǒng)計(jì)
- 數(shù)據(jù)源初始化耗時(shí)統(tǒng)計(jì)
- Runner / Listener 執(zhí)行耗時(shí)統(tǒng)計(jì)
示例代碼:
public static void main(String[] args) {
SpringApplication application = new SpringApplication(OrderApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
}
Actuator 暴露配置示例:
management.endpoints.web.exposure.include=startup,health,info
9.2 公共 SDK 不要無腦自動(dòng)裝配
公共 SDK、starter、組件包應(yīng)該遵循:
- 按需引入;
- 按條件裝配;
- 不要讓訂單服務(wù)加載用戶服務(wù)、營(yíng)銷服務(wù)、報(bào)表服務(wù)的一堆無關(guān) Bean;
- 對(duì)自動(dòng)配置類加條件注解。
示例:
@Configuration
@ConditionalOnProperty(prefix = "user.sdk", name = "enabled", havingValue = "true")
public class UserSdkAutoConfiguration {
}
9.3 啟動(dòng)任務(wù)必須分級(jí)
可以把啟動(dòng)任務(wù)分成三類:
| 類型 | 是否阻塞啟動(dòng) | 示例 |
|---|---|---|
| 核心任務(wù) | 必須阻塞 | 初始化路由、加載核心配置、校驗(yàn)關(guān)鍵依賴 |
| 可延遲任務(wù) | 不應(yīng)阻塞 | 緩存預(yù)熱、非核心字典加載、本地文件預(yù)解析 |
| 后臺(tái)任務(wù) | 異步執(zhí)行 | 定時(shí)報(bào)表初始化、歷史數(shù)據(jù)掃描、非實(shí)時(shí)同步 |
原則:
接口可用之前必須完成的任務(wù)才允許阻塞啟動(dòng);其他任務(wù)盡量異步化或延遲執(zhí)行。
10. 面試版標(biāo)準(zhǔn)回答
如果面試官問:
Spring Boot 啟動(dòng) 90 秒,如何優(yōu)化到 15 秒?
可以這樣回答:
我會(huì)先做啟動(dòng)耗時(shí)診斷,而不是直接盲目調(diào)參數(shù)。比如接入 Spring Boot Actuator 的 /actuator/startup 或 APM,看啟動(dòng)時(shí)間主要消耗在哪些階段。常見大頭有三個(gè):第一是 Bean 掃描和初始化,第二是數(shù)據(jù)源連接池初始化,第三是 ApplicationRunner、CommandLineRunner 或監(jiān)聽器里同步執(zhí)行了耗時(shí)任務(wù)。
如果發(fā)現(xiàn) Bean 初始化很慢,我會(huì)先排查是否引入了無關(guān) SDK 或無關(guān) @Configuration,通過拆分 starter、條件裝配或 @ComponentScan excludeFilters 減少無效 Bean。然后根據(jù)業(yè)務(wù)情況開啟 spring.main.lazy-initialization=true,把非必要 Bean 延遲到首次使用時(shí)再初始化,但開啟前要先檢查循環(huán)依賴,避免問題延遲到運(yùn)行時(shí)暴露。
如果數(shù)據(jù)源初始化慢,我會(huì)檢查 HikariCP 連接池配置,例如將 spring.datasource.hikari.minimum-idle 調(diào)低甚至設(shè)為 0,避免啟動(dòng)階段預(yù)創(chuàng)建大量連接。最后檢查 ApplicationRunner、CommandLineRunner、ApplicationListener 中是否有讀文件、遠(yuǎn)程調(diào)用、緩存預(yù)熱等非核心操作,把它們改成異步執(zhí)行或延后執(zhí)行。
整體思路是:先可觀測(cè)定位瓶頸,再按 Bean、數(shù)據(jù)源、啟動(dòng)任務(wù)三個(gè)方向逐項(xiàng)優(yōu)化,并用數(shù)據(jù)驗(yàn)證優(yōu)化效果。
11. 一句話總結(jié)
這個(gè)視頻真正想表達(dá)的是:
Spring Boot 啟動(dòng)優(yōu)化不是背配置,而是先定位耗時(shí),再針對(duì) Bean 初始化、數(shù)據(jù)源連接、啟動(dòng)任務(wù)阻塞這三個(gè)大頭逐步優(yōu)化。
以上就是SpringBoot啟動(dòng)慢的優(yōu)化指南的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot啟動(dòng)慢優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring?Cloud?Stream實(shí)現(xiàn)數(shù)據(jù)流處理
Spring?Cloud?Stream的核心是Stream,準(zhǔn)確來講Spring?Cloud?Stream提供了一整套數(shù)據(jù)流走向(流向)的API,?它的最終目的是使我們不關(guān)心數(shù)據(jù)的流入和寫出,而只關(guān)心對(duì)數(shù)據(jù)的業(yè)務(wù)處理,本文給大家介紹了Spring?Cloud?Stream實(shí)現(xiàn)數(shù)據(jù)流處理,需要的朋友可以參考下2024-11-11
Eclipse搭建spring開發(fā)環(huán)境圖文教程(推薦)
下面小編就為大家?guī)硪黄狤clipse搭建spring開發(fā)環(huán)境圖文教程(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07
Spring Eureka 未授權(quán)訪問漏洞修復(fù)問題小結(jié)
項(xiàng)目組使用的 Spring Boot 比較老,是 1.5.4.RELEASE ,最近被檢測(cè)出 Spring Eureka 未授權(quán)訪問漏洞,這篇文章主要介紹了Spring Eureka 未授權(quán)訪問漏洞修復(fù)問題小結(jié),需要的朋友可以參考下2024-04-04
SpringBoot項(xiàng)目中java -jar xxx.jar沒有主清單屬性的解決方法
這篇文章主要給大家介紹了SpringBoot項(xiàng)目中java -jar xxx.jar沒有主清單的解決方法,文中通過代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-01-01
Java并發(fā)程序刺客之假共享的原理及復(fù)現(xiàn)
前段時(shí)間在各種社交平臺(tái)“雪糕刺客”這個(gè)詞比較火,而在并發(fā)程序中也有一個(gè)刺客,那就是假共享。本文將通過示例詳細(xì)講解假共享的原理及復(fù)現(xiàn),需要的可以參考一下2022-08-08
SpringBoot熱部署Springloaded實(shí)現(xiàn)過程解析
這篇文章主要介紹了SpringBoot熱部署Springloaded實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
java編程經(jīng)典案例之基于斐波那契數(shù)列解決兔子問題實(shí)例
這篇文章主要介紹了java編程經(jīng)典案例之基于斐波那契數(shù)列解決兔子問題,結(jié)合完整實(shí)例形式分析了斐波那契數(shù)列的原理及java解決兔子問題的相關(guān)操作技巧,需要的朋友可以參考下2017-10-10

