spring版本 jdk8_从JDK8升级到JDK11,看这篇就足够了
一些背景
在背景知識,我們會討論一些關于新的JDK Release周期,OpenJDK特性歸一化,LTS(Long-term support長期支持版本)的事情。
1. 新的發布周期
這個就可以長話短說了,反正我們知道如下兩點就好:
- 每六個月發布一個大更新(就是每年的3月還有9月)
- 對于每個大版本更新,會有兩次小版本更新(在發布后一個月或者四個月之后)
2. OpenJDK已可以作為新的線上標準JDK
在2018.9之前,Oracle JDK是大家普遍運用于線上的JDK,OpenJDK的特性并不完全,并且Oracle JDK號稱做了很多優化。在2018.9之后,Oracle JDK正式商用(開發不收費,但是運行線上業務收費)。但是與此同時,Oracle宣布,OpenJDK與Oracle JDK在功能上不會有區別。并且,OpenJDK 11 RTS將會由紅帽社區進行維護。這樣,更加增加了可靠性與保證問題的及時解決。
我們可以在線上使用OpenJDK,開發時,使用任意的JDK。
3. LTS(Long-term support長期維護)版本
對于商業版的JDK,不同的廠商都將長期維護版本定在JDK 11/17/23/...
對于OpenJDK,社區說,對于這些版本,至少會提供四年的維護更新時間。每個長期維護版本都會有一個固定的管理者,對于OpenJDK11,應該就是紅帽社區。現在源代碼搞定了,但是,我們應該從哪里獲取編譯好的OpenJDK呢?這個可以交給AdoptOpenJDK,它會一直收集不同版本的OpenJDK以及全平臺的build好的OpenJDK
4. Amazon Corretto
AWS也提供了自己的OpenJDK,Amazon Corretto:
- 基于OpenJDK,采取GPL+CE協議,做了一些安全性,性能和穩定性優化,并且修復了一些bug
- 支持linux,MAC OS還有Windows操作系統
- 長期支持Java 8并且至少到2023年
- 從2019年開始支持Java 11并且至少到2024
- 季度更新,并且伴隨一些緊急bug修復的更新
OpenJDK社區的FAQ部分曾經提到:“Amazon從2017年開始貢獻OpenJDK,并且計劃開始大量貢獻”。我猜Amazon會把他們在Corretto上面做的優化,合并到OpenJDK源碼中,即使沒有,Corretto也是開源的,遲早會有人參考并在OpenJDK源碼上進行修改。同時也說明,OpenJDK的更新也會及時被合并到Corretto中。
準備遷移
1. 更新好開發環境以及編譯環境
各種常用工具,建議升級到如下版本以后:
- IntelliJ IDEA: 2018.2
- Eclipse: Photon 4.9RC2 with Java 11 plugin
- Maven: 3.5.0
- Maven compiler plugin: 3.8.0
- surefire and failsafe: 2.22.0
- Gradle: 5.0
對于如下工具,由于已經不再維護,需要替換成其他工具:
- FindBugs(靜態代碼bug發現): 用SpotBugs替換。
- Cobertura(代碼測試覆蓋率):用Jacoco替換
同時由于在Java 9 之后,每六個月bytecode level會提升一次。如果你依賴的庫有處理字節碼相關的庫,應該注意下版本升級,例如:
- 對于直接操作字節碼的庫,如果你升級了JDK,那么最好也跟著升級這些庫:ASM (7.0), Byte Buddy (1.9.0), cglib (3.2.8), or Javassist (3.23.1-GA).這些版本是OpenJDK11適用的版本
- 如果你使用的庫依賴了上面提到的操作字節碼的庫,那么也需要注意下版本依賴,看依賴的操作字節碼的庫是否升級到了上面提到的版本。對于Spring,最好采用5.1以后的版本, Mockito則是2.20.0以后的版本
2. 引入JPMS后,相關的遷移工作
2.1. Java EE相關模塊默認不在Java包里面了,相關的類需要增加額外依賴或者替換成其他的類
如果你的項目中使用了這些類,那么在編譯階段就會報錯,例如:
error: package javax.xml.bind does not exist import javax.xml.bind.JAXBException;^如果你是用JDK 8編譯成功,拿到JDK 11運行,就會報錯:
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXBExceptionat monitor.Main.main(Main.java:27) Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBExceptionat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185)at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)... 1 more以下是相關移除列表還有解決方案
一個建議就是,在你的項目中如果沒有沖突,建議都加上這些依賴。
2.2. 模塊可見性導致的內部API不能調用的問題
這個在我另一篇文章也說過:https://zhanghaoxin.blog.csdn.net/article/details/90514045
在Java9之后引入了模塊化的概念,是將類型和資源封裝在模塊中,并僅導出其他模塊要訪問其公共類型的軟件包。如果模塊中的軟件包未導出或打開,則表示模塊的設計人員無意在模塊外部使用這些軟件包。 這樣的包可能會被修改或甚至從模塊中刪除,無需任何通知。 如果仍然使用這些軟件包通過使用命令行選項導出或打開它們,可能會面臨破壞應用程序的風險!
對于這種限制,在編譯階段,可能會有類似下面的報錯:
error: package com.sun.imageio.plugins.jpeg is not visible import com.sun.imageio.plugins.jpeg.JPEG;^(package com.sun.imageio.plugins.jpeg is declaredin module java.desktop, which does not export it)如果是反射的調用,可能在運行階段有類似于如下的報警:
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by j9ms.internal.JPEG(file:...) to field com.sun.imageio.plugins.jpeg.JPEG.TEM WARNING: Please consider reporting thisto the maintainers of j9ms.internal.JPEG WARNING: Use --illegal-access=warn to enable warningsof further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release # here's the reflective access to the static field com.sun.imageio.plugins.jpeg.JPEG.TEM對于這種錯誤,我們最好是更換API,如果難以實現,則可以通過添加編譯以及啟動參數解決。
我們需要的參數是:
- --add-exports選項:模塊聲明中的exports語句將模塊中的包導出到所有或其他模塊,因此這些模塊可以使用該包中的公共API。 如果程序包未由模塊導出,則可以使用-add-exports的命令行選項導出程序包:
如果設置target-module-list為ALL-UNNAMED,那么所有Classpath下的module,都可以訪問source-module中的pakage包下的公共API
- --add-opens選項: 模塊聲明中的opens語句使模塊里面的包對其他模塊開放,因此這些模塊可以在運行期使用深層反射訪問該程序包中的所有成員類型。 如果一個模塊的包未打開,可以使用--add-opens命令行選項打開它。 其語法如下:
如果設置target-module-list為ALL-UNNAMED,那么所有Classpath下的module,都可以訪問source-module中的pakage包下的所有成員類型
對于編譯階段,也就是javac命令,我們只需要添加--add-exports,對于上面的例子,就是:
javac --add-exports java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED對于運行階段,也就是java命令,我們最好把--add-exports和--add-open都加上,對于上面的例子,就是:
java --add-exports java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED --add-open java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED這樣,在運行階段,首先不會有禁止訪問報錯,同時也不會有警告。
同時,為了在運行期能找到所有需要添加的模塊和包,可以通過添加--illegal-access=${value}來檢查。這個value可以填寫:
- permit: 未來可能會移除。僅在第一次反射調用內部api的時候報警
- warn:每次次反射調用內部api的時候報警
- debug:在warn的基礎上,加上堆棧輸出
- deny: 拒絕所有非法反射訪問內部api
我們可以設置--illegal-access=deny來知道我們需要添加的所有--add-export和--add-open包。
2.3 通過JDK11內置jdeps工具查找過期以及廢棄API以及對應的替換
這個也在我另一篇文章提到過:https://zhanghaoxin.blog.csdn.net/article/details/100732605
jdeps --jdk-internals -R --class-path 'libs/*' $projectlibs是你的所有依賴的目錄,$project是你的項目jar包,示例輸出:
... JDK Internal API Suggested Replacement ---------------- --------------------- sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8 sun.reflect.Reflection Use java.lang.StackWalker @since 9在這里簡單提一些在JDK11過期,但是JDK8使用的API:
- sun.misc.Base64 (替換成 java.util.Base64)
- com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel (替換成 javax.swing.plaf.nimbus.NimbusLookAndFeel)
- java.util.LogManager, java.util.jar.Pack200.Packer類 Unpacker: addPropertyChangeListener 和 removePropertyChangeListener這兩個方法已經移除
- java.lang.Runtime類: methods getLocalizedInputStream 和 getLocalizedOutputStream方法已經移除
- SecurityManager的操作方法已經整體移除
2.4. ClassLoader變化帶來的URLClassLoader的變化
Java 8的ClassLoader流程:
- bootstrap classloader加載rt.jar,jre/lib/endorsed
- ext classloader加載jre/lib/ext
- application classloader加載-cp指定的類
java9及之后的classloader流程:
- bootstrap classloader加載lib/modules
- ext classloader更名為platform classloader,加載lib/modules
- application classloader加載-cp,-mp指定的類
同時,我們注意到,JDK9開始,AppClassLoader他爹不再是 URLClassLoader
一般熱部署,插件部署,都會使用到AppClassLoader,例如Spring-Boot的熱部署,老版本的會報異常:
Exception in thread "main" java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoaderat org.springframework.boot.devtools.restart.DefaultRestartInitializer.getUrls(DefaultRestartInitializer.java:93)at org.springframework.boot.devtools.restart.DefaultRestartInitializer.getInitialUrls(DefaultRestartInitializer.java:56)at org.springframework.boot.devtools.restart.Restarter.<init>(Restarter.java:140)at org.springframework.boot.devtools.restart.Restarter.initialize(Restarter.java:546)at org.springframework.boot.devtools.restart.RestartApplicationListener.onApplicationStartingEvent(RestartApplicationListener.java:67)at org.springframework.boot.devtools.restart.RestartApplicationListener.onApplicationEvent(RestartApplicationListener.java:45)at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:122)at org.springframework.boot.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:69)at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:48)at org.springframework.boot.SpringApplication.run(SpringApplication.java:292)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)at com.asofdate.AsofdateMain.main(AsofdateMain.java:18)這是主要是因為AppClassLoader不再是URLClassLoader的子類導致的。
之前對于動態加載的類,我們總是通過將這個類通過反射調用URLClassLoader加到classpath里面進行加載。這么加載在JDK11中已經無法實現,并且這樣加載的類不能卸載。 對于動態加載的類,我們在OpenJDK11中只能自定義類加載器去加載,而不是通過獲取APPClassLoader去加載。同時,這么做也有助于你隨時能將動態加載的類卸載,因為并沒有加載到APPClassLoader。
建議使用自定義的類加載器繼承SecureClassLoader去加載類:java.security.SecureClassLoader
最后,如果你想訪問classpath下的內容,你可以讀取環境變量:
String pathSeparator = System.getProperty("path.separator"); String[] classPathEntries = System.getProperty("java.class.path").split(pathSeparator);2.5. 過期啟動參數修改
JDK 8 到JDK 11有很多參數變化,可以總結為兩類參數的變化,一是GC相關的(GC配置調優更加簡單),二是日志相關的,日志統一到了一起,不像之前那么混亂
具體請參考:
每個說明參考三部分:
3. 一些框架的OpenJDK11兼容問題持續收集(持續更新中)
總結
以上是生活随笔為你收集整理的spring版本 jdk8_从JDK8升级到JDK11,看这篇就足够了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python 笔记本_Python笔记本
- 下一篇: spring 事务隔离级别和传播行为_S