1、前言
如果你仍在使用JDK8,那你是否曾經遇到過OutOfMemoryError的問題?你是否曾經為JVM的調優問題感到困擾?本篇文章將為你介紹一種能夠提供百倍效能提升的垃圾回收器,也許能夠解決你的問題。
上篇文章給大家帶來了JDK8升級JDK11的最全實踐,相信大家閱讀後已經對JDK11有了比較深入的瞭解。2021年9月14日,Oracle釋出了可以長期支援的JDK17版本,那麼從JDK11到JDK17,到底帶來了哪些特性呢?亞毫秒級的ZGC效果到底怎麼樣呢?值得我們升級嗎?而且升級過程會遇到哪些問題呢?帶著這些問題,本篇文章將帶來完整的JDK11升級JDK17最全實踐。
2、為什麼升級JDK17
1)長期支援版本
JDK17是Oracle官方在2021年9月14日釋出的一個長期支援(LTS)版本,意味著它將獲得長期的更新和支援,有助於保持程式的穩定性和可靠性。
2)效能提升
更好的垃圾回收器。綜合評估,從Java 8 升級到 Java 11,G1GC平均速度提升16.1%,ParallelGC為4.5%,從Java 11 升級到 Java 17,G1GC平均速度提升8.66%,ParallelGC為6.54%(基於OptaPlanner的用例基準測試表明)
最大的亮點是帶來了穩定版的ZGC垃圾回收器,達到亞毫秒級停頓。
3)新語法和特性
Switch表示式簡化、Text Blocks文字塊、instanceof 的模式匹配升級和NullPointerException提示資訊改進等
4)支援最新的技術和框架
Spring framework6 和Spring Boot3 都預設使用 Java 17作為最低版本
3、升級後壓測效果
先給出結論: 1、JDK17相對於JDK8和JDK11,所有垃圾回收器的效能都有很明顯的提升,特別是穩定版的ZGC垃圾回收器2、不論任何機器配置下,都推薦使用ZGC,ZGC的停頓時間達到亞毫秒級,吞吐量也比較高
我在JDOS平臺上選擇了不同配置的機器(2C4G、4C8G、8C16G),並分別使用JDK8、JDK11和JDK17進行部署和壓測。
整個壓測過程限時60分鐘,用180個虛擬使用者併發請求一個介面,每次介面請求都建立512Kb的資料。最終產出不同GC回收器的各項指標資料,來分析GC的效能提升效果。
以下是壓測的效能情況:
4、OracleJDK 和 OpenJDK 的選擇
2021年9月,Oracle宣佈JDK17可以免費商用,直到下一個 LTS 版本之後繼續提供整整一年,同時Oracle 將繼續按照自 Java 9 以來的相同版本和時間表提供GPL下的Oracle OpenJDK 版本。
2023年9月,OracleJDK釋出了新的LTS版本 JDK21,這就意味著從2024年9月開始,在生產環境使用 OracleJDK17 將需要付費。
參考: https://www.oracle.com/hk/java/technologies/downloads/#java17
OracleJDK和OpenJDK這兩個之間沒有真正的技術差別,因為針對Oracle JDK構建過程是基於OpenJDK的。自從JDK11開始,OracleJDK和OpenJDK在功能上基本相同,所以推薦使用 OpenJDK17 或其他開源的JDK版本,這些開源版本都是基於OpenJDK構建並提供長期支援的,比如:AdoptOpenJDK、RedHatOpenJDK。
官方參考: https://blogs.oracle.com/java/post/oracle-jdk-releases-for-java-11-and-later
5、JDK11到JDK17帶來了哪些新特性
5.1、JVM改進
1、ZGC垃圾回收器從實驗性功能更改為正式產品功能,從JDK11引入以來,經過持續的迭代升級,目前已經足夠穩定。需要手動開啟,開啟方式:-XX:+UseZGC
2、G1垃圾回收器仍然作為預設垃圾回收器,進行改進升級,主要包括可中止的混合收集集合、NUMA 可識別記憶體分配等
3、JDK14開始刪除 CMS 垃圾回收器
4、JDK14開始棄用 ParallelScavenge 和 SerialOld GC 的組合使用
5、JDK15禁用偏向鎖,預設禁用:-XX:+UseBiasedLocking
6、NullPointerException 提示資訊改進
JDK14以前的出現NullPointerException時,只能定位到所在異常行,無法定位具體是哪個變數。改進後的NullPointerException,可以清晰描述具體變數,提升了空指標異常的可讀性。
5.2、新語法特性
5.2.1、Switch表示式簡化
switch表示式帶來了簡化式的編碼方式,提供了新的分支切換方式,即 -> 符號,右則表示式方法體在執行完分支方法之後,自動結束 switch 分支,同時 -> 右則方法塊中可以是表示式、程式碼塊或者是手動丟擲的異常
參考: https://openjdk.org/jeps/361
傳統寫法
新寫法
5.2.2、Text Blocks文字塊(5星推薦)
參考: https://openjdk.org/jeps/378
透過編寫 """,來減少跳脫字元和換行符,達到簡化程式碼和提高程式碼可讀性的目的
5.2.3、Record型別
參考: https://openjdk.org/jeps/395
record 是 JDK 14 引入的關鍵字,用於宣告不可變的資料類。它適用於儲存純粹的值型別資料,如介面傳輸資料、座標點和只讀的日誌記錄。與 lombok 相比,record 簡化了定義純粹資料型別的過程。由於 record 類是不可變的,成員變數只能設定一次且無法更改,無需提供顯式的 setter() 方法。
1、定義Point類,使用關鍵字record,未定義get/set
2、檢視編譯後的位元組碼檔案
3、使用Point類
5.2.4、instanceof 的模式匹配升級
參考: https://openjdk.org/jeps/394
5.2.5、密封的類和介面
參考: https://openjdk.org/jeps/409
JDK15開始,引入了sealed普通類或介面類,這些類只允許被指定的類或者interface進行擴充套件和實現。
使用修飾符sealed,您可以將一個類宣告為密封類。密封的類使用關鍵字permits列出可以直接擴充套件它的類。子類可以是最終的,非密封的或密封的
比較實用的一個特性,可以用來限制類的層次結構
5.2.6、其他最佳化和升級
感興趣的同學,推薦閱讀OpenJDK官方文件說明,從JDK11到JDK17的改動: https://openjdk.org/projects/jdk/17/jeps-since-jdk-11
6、升級步驟
6.1、JDK選擇
OpenJDK17下載:https://jdk.java.net/archive/
行雲映象:jdt-base-tomcat/java-jdt-centos7.4-openjdk-17.0.2-tomcat8.0.53
6.2、pom編譯配置升級
maven編譯所需JDK升級至17
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
6.3、SpringBoot升級
SpringBoot版本升級到2.7.15,Spring版本升級為5.3.29
為什麼不升級到SpringBoot3?
Spring Boot 3.0最低要求 Java 17,SpringBoot3.0帶來了很多變化,和SpringBoot2差異較大。 考慮到公司很多中介軟體都是基於SpringBoot2構建的,所以此處推薦升級到SpringBoot2的最高版本2.7.15。
POM升級
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
</parent>
也可以透過設定dependencyManagement的方式:
<properties>
<!-- 框架版本配置-->
<springboot-version>2.7.15</springboot-version>
<springframework.version>5.3.29</springframework.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${springboot-version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${springframework.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
參考:
spring升級指南: https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions
springboot版本官網: https://spring.io/projects/spring-boot#learn
迴圈依賴問題
SpringBoot升級到2.7.15後,如果應用中存在迴圈依賴的問題,啟動時會報如下錯誤:
原因:官方文件不鼓勵迴圈依賴引用,預設情況下是禁止的
解決方案:
第一種:推薦更新應用中bean的依賴關係來解決
第二種:配置檔案中加入以下配置,為了和舊版本保持一致,此配置推薦新增
#放開迴圈依賴
spring.main.allow-circular-references=true
6.4、常用中介軟體升級
6.4.1、Lombok版本升級到1.18.20以上
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
如果不升級,編譯時會報錯如下:
6.4.2、swgger問題,springfox3.0.0和springboot2.7版本不相容
異常:
Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException:
Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null
解決方案:
properties配置(感謝 zhaoyongping8 驗證提供):
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
/**
* 增加如下配置可解決Spring Boot 2.7.15 與Swagger 3.0.0 不相容問題
**/
@Bean
public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(