原文地址:https://blog.codefx.org/java/java-11-migration-guide/。 在原文的基礎上,增長了一些我遇到的具體的坑還有在特定場景下的解決方案,供你們參考html
在背景知識,咱們會討論一些關於新的JDK Release週期,OpenJDK特性歸一化,LTS(Long-term support長期支持版本)的事情。java
這個就能夠長話短說了,反正咱們知道以下兩點就好:linux
在OpenJDK 11以前,Oracle JDK是你們廣泛運用於線上的JDK,OpenJDK的特性並不徹底,而且Oracle JDK號稱作了不少優化。在OpenJDK 11以後,Oracle JDK正式商用(開發不收費,可是運行線上業務收費)。可是與此同時,Oracle宣佈,OpenJDK與Oracle JDK在功能上不會有區別。而且,OpenJDK 11 RTS將會由紅帽社區進行維護。這樣,更加增長了可靠性與保證問題的及時解決。spring
咱們能夠在線上使用OpenJDK,開發時,使用任意的JDK。bootstrap
對於商業版的JDK,不一樣的廠商都將長期維護版本定在JDK 11/17/23/...api
對於OpenJDK,社區說,對於這些版本,至少會提供四年的維護更新時間。每一個長期維護版本都會有一個固定的管理者,對於OpenJDK11,應該就是紅帽社區。如今源代碼搞定了,可是,咱們應該從哪裏獲取編譯好的OpenJDK呢?這個能夠交給AdoptOpenJDK,它會一直收集不一樣版本的OpenJDK以及全平臺的build好的OpenJDK安全
AWS也提供了本身的OpenJDK,Amazon Corretto:oracle
OpenJDK社區的FAQ部分曾經提到:「Amazon從2017年開始貢獻OpenJDK,而且計劃開始大量貢獻」。我猜Amazon會把他們在Corretto上面作的優化,合併到OpenJDK源碼中,即便沒有,Corretto也是開源的,早晚會有人蔘考並在OpenJDK源碼上進行修改。同時也說明,OpenJDK的更新也會及時被合併到Corretto中。app
各類經常使用工具,建議升級到以下版本之後:框架
對於以下工具,因爲已經再也不維護,須要替換成其餘工具:
同時因爲在Java 9 以後,每六個月bytecode level會提高一次。若是你依賴的庫有處理字節碼相關的庫,應該注意下版本升級,例如:
若是你的項目中使用了這些類,那麼在編譯階段就會報錯,例如:
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/JAXBException at monitor.Main.main(Main.java:27) Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException at 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
如下是相關移除列表還有解決方案
<dependency> <groupId>com.sun.activation</groupId> <artifactId>javax.activation</artifactId> <version>1.2.0</version> </dependency>
<dependency> <groupId>javax.transaction</groupId> <artifactId>javax.transaction-api</artifactId> <version>1.2</version> </dependency>
<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.2.8</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.2.8</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.8</version> </dependency>
<dependency> <groupId>com.sun.xml.ws</groupId> <artifactId>jaxws-ri</artifactId> <version>2.3.0</version> <type>pom</type> </dependency>
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.1</version> </dependency>
一個建議就是,在你的項目中若是沒有衝突,建議都加上這些依賴。
這個在我另外一篇文章也說過: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 declared in 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 this to the maintainers of j9ms.internal.JPEG WARNING: Use --illegal-access=warn to enable warnings of 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
的命令行選項導出程序包:--add-exports <source-module>/<package>=<target-module-list>
若是設置target-module-list爲ALL-UNNAMED,那麼全部Classpath下的module,均可以訪問source-module中的pakage包下的公共API
--add-opens
選項: 模塊聲明中的opens語句使模塊裏面的包對其餘模塊開放,所以這些模塊能夠在運行期使用深層反射訪問該程序包中的全部成員類型。 若是一個模塊的包未打開,可使用--add-opens命令行選項打開它。 其語法以下:--add-opens <source-module>/<package>=<target-module-list>
若是設置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能夠填寫:
咱們能夠設置--illegal-access=deny
來知道咱們須要添加的全部--add-export
和--add-open
包。
這個也在我另外一篇文章提到過: https://zhanghaoxin.blog.csdn.net/article/details/100732605
jdeps --jdk-internals -R --class-path 'libs/*' $project
libs是你的全部依賴的目錄,$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:
Java 8的ClassLoader流程:
java9及以後的classloader流程:
同時,咱們注意到,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.URLClassLoader at 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);
JDK 8 到JDK 11有不少參數變化,能夠總結爲兩類參數的變化,一是GC相關的(GC配置調優更加簡單),二是日誌相關的,日誌統一到了一塊兒,不像以前那麼混亂
具體請參考:
每一個說明參考三部分: