1. 簡介
本教程將介紹各種配置和設置,以幫助降低 Spring Boot 的啓動時間。首先,我們將探討 Spring 相關的配置。其次,我們將介紹 Java 虛擬機選項。最後,我們將探討如何利用 GraalVM 和原生鏡像編譯技術進一步降低啓動時間。
2. Spring Tweaks
在我們開始之前,我們先創建一個測試應用程序。我們將使用 Spring Boot 2.5.4 版本,並添加 Spring Web、Spring Actuator 和 Spring Security 作為依賴。在 <em pom.xml</em>> 中,我們將添加 <em spring-boot-maven-plugin</em>> 並配置將其應用程序打包為 jar 文件:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<finalName>springStartupApp</finalName>
<mainClass>com.baeldung.springStart.SpringStartApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>我們使用標準命令 java -jar 運行我們的 jar 文件,並監控應用程序的啓動時間:
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 3.403 seconds (JVM running for 3.961)
我們可以看到,我們的應用程序大約在 3.4 秒處啓動。我們將使用這個時間作為未來調整的參考。
2.1. 延遲初始化
Spring Framework 支持延遲初始化。延遲初始化意味着 Spring 在啓動時不會創建所有 Bean。同時,Spring 在需要該 Bean 時才會注入依賴項。 自 Spring Boot 2.2 版本起,可以使用 <em application.properties</em> 來啓用延遲初始化:
spring.main.lazy-initialization=true構建一個新的 JAR 文件並在先前示例中啓動它後,啓動時間略有改善:
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 2.95 seconds (JVM running for 3.497)
根據我們的代碼庫大小,延遲初始化可以顯著減少啓動時間。 減少量取決於應用程序的依賴關係圖。
此外,在利用 DevTools 熱重啓功能時,延遲初始化也有優勢。 頻繁的熱重啓與延遲初始化結合使用,可以使 JVM 更好地優化代碼。
然而,延遲初始化也存在一些缺點。 最主要的缺點是應用程序將首次請求響應變慢。 因為 Spring 需要時間來初始化所需的 Bean,另一個缺點是我們在啓動時可能會錯過一些錯誤。 這可能導致在運行時出現 ClassNotFoundException。
2.2. 排除不必要的自動配置
Spring Boot 始終秉持着“約定優於配置”的原則。 Spring 可能會初始化應用程序不需要的 Bean。我們可以通過啓動日誌檢查所有自動配置的 Bean。 將 org.springframework.boot.autoconfigure 的日誌級別設置為 DEBUG,在 application.properties 中:
logging.level.org.springframework.boot.autoconfigure=DEBUG在日誌中,我們將看到專門用於自動配置的新行,從以下開始:
============================
CONDITIONS EVALUATION REPORT
============================
使用本報告,我們可以排除應用程序配置的一部分。要排除配置的一部分,我們使用 @EnableAutoConfiguration 註解:
@EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class, JvmMetricsAutoConfiguration.class,
LogbackMetricsAutoConfiguration.class, MetricsAutoConfiguration.class})如果我們排除 Jackson JSON 庫以及我們未使用的部分指標配置,就可以節省一些啓動時間:
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 3.183 seconds (JVM running for 3.732)
2.3. 其他小調整
Spring Boot 包含一個嵌入式 Servlet 容器。默認情況下,我們使用 Tomcat。雖然 Tomcat 在大多數情況下已經足夠,但其他 Servlet 容器可能性能更優。在測試中,來自 JBoss 的 Undertow 的性能優於 Tomcat 或 Jetty。它需要的內存更少,平均響應時間也更好。要切換到 Undertow,我們需要更改 pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>以下改進可以應用於類路徑掃描。Spring 類路徑掃描具有快速啓動的特性。通過在大型代碼庫中創建靜態索引,我們可以提高啓動時間。需要添加對 spring-context-indexer 的依賴,以生成索引。Spring 不需要任何額外的配置。在編譯時,Spring 會在 META-INF\spring.components 中生成一個額外的文件,並在啓動時自動使用它:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>${spring.version}</version>
<optional>true</optional>
</dependency>由於我們只有一個 Spring 組件,這種調整在我們的測試中沒有產生顯著結果。
請注意,spring-context-indexer 自 Spring 6.1 版本起已棄用。有關更多信息,請參閲 此處。
接下來,application.properties (或 .yml) 文件的有效位置有幾個。最常見的位於 classpath 根目錄或與 jar 文件相同的文件夾中。通過使用 spring.config.location 參數設置明確路徑,可以避免在多個位置搜索,並節省數毫秒的搜索時間:
java -jar .\target\springStartupApp.jar --spring.config.location=classpath:/application.properties最後,Spring Boot 提供了一些 MBean 用於使用 JMX 監控我們的應用程序。 完全禁用 JMX 並避免創建這些 Bean 的開銷:
spring.jmx.enabled=false3. JVM 調整
JVM 調整是指通過修改 JVM 的配置和參數,以優化應用程序的性能。這些調整通常針對特定應用程序的硬件環境、負載和需求進行定製。
以下是一些常見的 JVM 調整:
- 垃圾回收器選擇: 不同的垃圾回收器(如 G1GC、ZGC、Parallel GC)具有不同的特性和性能表現。選擇合適的垃圾回收器可以顯著影響應用程序的性能和穩定性。
- 堆大小調整: 調整 Java 堆的大小可以避免頻繁的垃圾回收,提高應用程序的吞吐量。
- 線程池配置: 優化線程池的大小和配置,以提高併發性能。
- JIT 編譯器優化: 調整 JIT 編譯器的參數,以提高代碼執行效率。
- 監控和分析: 使用監控工具跟蹤 JVM 的性能指標,並根據分析結果進行調整。
這些調整需要根據實際情況進行測試和驗證,以確保它們能夠提高應用程序的性能,而不會引入新的問題。
3.1. <em V 驗證標誌
此標誌設置字節碼驗證模式。字節碼驗證可以確保類是否正確格式化並且符合 JVM 規範約束。我們在 JVM 啓動時設置此標誌。
有幾種選項可供此標誌使用:
- -Xverify 是默認值,並且在所有非啓動類上啓用驗證。
- -Xverify:all 啓用所有類的驗證。 此配置會對啓動時產生顯著的負面性能影響。
- -Xverify:none (或 -Xnoverify)。 此選項完全禁用驗證器,從而顯著縮短啓動時間。
java -jar -noverify .\target\springStartupApp.jar 我們將會收到 JVM 的警告,表明此選項已過時。同時,啓動時間也會減少:
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 3.193 seconds (JVM running for 3.686)
此標誌引入了一個顯著的權衡。我們的應用程序在運行時可能因錯誤而崩潰,而我們可以在更早的時候捕獲到這個錯誤。這也是為什麼這個選項在 Java 13 中被標記為已棄用,並且將在未來的版本中被移除。
3.2. <em >TieredCompilation</em > 標誌
Java 7 引入了分層編譯。HotSpot 編譯器將使用不同級別的編譯對代碼進行處理。
正如我們所知,Java 代碼首先會被解釋成字節碼,然後字節碼會被編譯成機器碼。這個翻譯發生在方法級別。C1 編譯器在方法被調用一定次數後進行編譯。經過更多運行,C2 編譯器進一步提高性能。
通過使用 <em >-XX:-TieredCompilation</em > 標誌,我們可以禁用中間編譯層級。這意味着我們的方法將使用 C2 編譯器進行解釋或編譯以實現最大優化。這不會導致啓動速度降低。我們需要做的就是禁用 C2 編譯。我們可以使用 <em >-XX:TieredStopAtLevel=1</em > 選項來實現。與 <em >-noverify</em > 標誌結合使用,可以減少啓動時間。不幸的是,這會減慢 JIT 編譯器在後續階段的運行速度。
單獨使用 TieredCompilation 標誌可以帶來顯著的改進。
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 2.754 seconds (JVM running for 3.172)
為了獲得更大的效果,同時運行本部分中的兩個標誌位,可以進一步減少啓動時間:
java -jar -XX:TieredStopAtLevel=1 -noverify .\target\springStartupApp.jar
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 2.537 seconds (JVM running for 2.912)
4. Spring Native
原生鏡像是指使用即時編譯器的 Java 代碼編譯後打包成可執行文件。它不需要 Java 運行環境。 結果程序速度更快,對內存的依賴性更低,因為沒有 JVM 開銷。 GraalVM 項目引入了原生鏡像,並要求使用構建工具。
Spring Native 是一個實驗性模塊,它支持使用 GraalVM native-image 編譯器對 Spring 應用程序進行原生編譯。 即時編譯器在構建過程中執行多個任務,從而減少啓動時間(靜態分析、刪除未使用的代碼、創建固定類路徑等)。原生鏡像仍然存在一些限制:
- 它不支持所有 Java 功能
- 反射需要特殊配置
- 懶加載不可用
- Windows 兼容性存在問題。
要將應用程序編譯為原生鏡像,我們需要將 spring-aot 和 spring-aot-maven-plugin 依賴項添加到 pom.xml 中。 Maven 將在 package 命令在 target 文件夾中創建原生鏡像。
5. 結論
在本文中,我們探討了多種提高 Spring Boot 應用啓動時間的途徑。首先,我們介紹了各種有助於減少啓動時間的 Spring 相關特性。然後,我們展示了 JVM 相關的選項。最後,我們介紹了 Spring Native 以及原生鏡像的創建。